传送门:http://codeforces.com/contest/731/problem/D
题意:给你n个word,每个word都有若干个字母,现在要使得从上到下的word都必须小于或等于下一个word,你可以操作一次钥匙将所有word的所有字母都加上1,如果>c,就变为1,问是否有解,有的话随意输出,没有的话就输出-1
思路:
如果光暴力复杂度会达到O(nc),下面介绍两个技巧:
1.首先介绍下什么是差分法。比如给你一个长度为n的数组cnt,一开始全是0。现在如果让你从下标2~4的位置都+1,怎么做?cnt[2]++,cnt[5]--
数组变成了0 0 1 0 0 -1 0....,我们再进行for(int i=0;i<n;++i)cnt[i]+=cnt[i-1]; 现在变成了0 0 1 1 1 0 0...是不是2~4都+1了?
总结一下,差分的想法就是在区间a~b中+1,等价于cnt[a]++,cnt[b+1]--,然后再处理一边前缀和就行了,这样可以降低区间加减的复杂度
2.什么是线段扫描法?给你n个区间,问你有没有一个公共的区间是这所有n个区间的公共子区间。
首先把这几个区级排好,然后找一条竖着的直线,从左向右平移,然后看有没有一瞬间这个直线与所有区间都相交,及有n个交点。
这道题怎么应用上面两种技巧呢
首先对于相邻2个word,
1.如果word[1]>word[2],假设对应的那个字母为a[i],b[i](a[i]>b[i])那么我们需要操作钥匙,使得word[1]<word[2],因为前面的都一样,只需要考虑当前位置i,不难得出使用钥匙次数区间(c-a[i]+1,c-b[i]),对这个区间的cnt加1
2.当word[1]<word[2]的时候,同理可以得出使用钥匙次数区间(0,c-b[i])和(c-a[i]+1,c-1),对这个区间的cnt加1(注意如果比完了一直相等但是word[1]的长度>word[2],我们就可以直接输出-1了,因为不管操作多少次,前面的元素肯定相等,但始终word[1]>word[2]),这里我们遇到这种情况可以不使用钥匙,即不对cnt进行操作)
3.word[1]比word[2]短或者完全一样,我们对区间(0, c)的cnt都加1就好了
对区间的cnt加1可以用差分法实现,复杂度为O(1), 最后判断使用钥匙次数0~c-1这个区间中有没有cnt等于比较的次数n-1(这个n-1的得到用到了线段扫描的思想,需要仔细想想),所以如果其中出现始终word[1]>word[2]的情况是不可能有n-1的
代码:
#include <bits/stdc++.h>
using namespace std;
#define rep(i,k,n) for(int i=k;i<=n;i++)
const int N=5e5+10;
std::vector<int> words[N];
int cnt[2 * N], n, c;//cnt表示转几次是否满足要求
void cal(int a, int b){
int id = 0;//id表示失配位置
while(id < words[a].size() && id < words[b].size()){
if(words[a][id] != words[b][id])break;
id++;
}
if(id < words[a].size() && id < words[b].size()) { //失配的位置在a,b的中部
if(words[a][id] < words[b][id]){ //在失配位置是b的大于a的
cnt[0]++;
cnt[c - words[b][id] + 1]--;
cnt[c - words[a][id] + 1]++;
cnt[c]--;
}
else{ //在失配位置是b的小于a的
cnt[c - words[a][id] + 1]++;
cnt[c - words[b][id] + 1]--;
}
}
else if(id == words[a].size() && id != words[b].size()) { //a比b短
cnt[0]++; //a 123
cnt[c]++; //b 12345
}
else if(id != words[a].size() && id == words[b].size()) ; //a比b长
//a 12345
//b 123
else{ //a和b完全一样
cnt[0]++;
cnt[c]--;
}
}
int main(){
scanf("%d%d", &n, &c);
rep(i, 1, n){
int len, w;
scanf("%d", &len);
while(len--){
scanf("%d", &w);
words[i].push_back(w);
}
}
rep(i, 1, n - 1)cal(i, i + 1);
bool ok = false;
int sum = 0;
rep(i, 0, c - 1){
sum += cnt[i];
if(sum == n - 1){
ok = true;
printf("%d\n", i);
break;
}
}
if(!ok)printf("-1\n");
return 0;
}