【codeforces 731D】【差分+线段扫描 思维题】80-th Level Archeology【给你n个word,现在要使得word按字典序排列,随便操作多少次,每次可以使每个word+1】

传送门: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;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值