题解:UVA12572 RMQ Overkill
吐槽一下:题面复制不全,需要阅读 PDF 才能看懂题意。 ::::info[题意]{open} 给定一个由数字组成的字符串,询问其中每个子串中出现过的最小的一位数字和。 :::: 由于数据规模不能支持我们真的去查询每个子串(或者像原题所说的区间),我们需要一些更好的查询方式。
我们注意到一个区间的最小值取决于区间的最小值(废话),那么反过来看,是不是区间的最小值决定了区间的最小值(并非废话)?换句话说,考虑每个位置的值能够决定多少个区间的最小值,也就是对答案产生了多少贡献,就可以快速地统计最终答案了。
考虑一个问题:在一个区间中,有若干个点被打上了标记,那么至少包含有一个标记的点的区间有多少个?对于这个问题,为了不重复不遗漏地统计,我们考虑对每个标记点,钦定其为区间中最右边的标记点,假设这个区间为
注意到数据范围,数字范围是
::::info[模拟过程]
4
1231
我们先考虑完整区间以及数字 0,发现没有数字 0。
接下来考虑完整区间以及数字 1,发现第一位(下标个人习惯从 1 开始)是数字 1,下一个数字 1 在第四位,所以第一位的一贡献了
接下来考虑第四位的数字 1,后面没有数字 1 了,贡献了
接下来考虑区间
接下来考虑区间
我们得到最终答案为 14。 ::::
代码如下:
#include<bits/stdc++.h>
#define ll long long
#define MOD 1000000007
using namespace std;
string s;
ll n,answer,a[10004],last[10],jump[10004];
void f(ll l,ll r,ll k)
{
if(l>r||k>9)return;
ll lastl=l;
for(ll i=l;i<=r;i++)
{
if(a[i]==k)
{
answer+=k*(i-l+1)*(min(jump[i]-1,r)-i+1);
answer%=MOD;
f(lastl,i-1,k+1);//考虑更小的区间和更大的标记数字
lastl=i+1;//考虑更大数字时要绕过当前位
}
}
f(lastl,r,k+1);
}
int main()
{
while(cin>>n)
{
answer=0;
cin>>s;
for(ll i=0;i<n;i++)a[i+1]=s[i]-'0';//string->integers
memset(last,127,sizeof(last));
for(ll i=n;i>=1;i--)//预处理下一次出现本数字的位置
{
jump[i]=last[a[i]];
last[a[i]]=i;
}
f(1,n,0);
cout<<answer<<'\n';
}
}
最后斗胆评价一下题目难度,刚好是在我的舒适区内,能很快看出来但又要多想两下的程度,应该是普及+/提高左右?