文章目录

  • A. All-Star Game
  • C. Cinema
  • E. Enigmatic Partition
  • G. Game SET
  • H. hard String Problem
  • I. Interesting Computer Game
  • J. Jumping Points
  • K. Kabaleo Lite

A. All-Star Game

有 nnn 个球星和 mmm 个球迷,给出他们之间的初始关系,如果 aaa 是 bbb 的球迷那么 aaa 喜欢看 bbb 的比赛,如果 a,ca,ca,c 都喜欢看 bbb 的比赛,并且 ccc 喜欢看 ddd 的比赛,那么 aaa 也喜欢看 ddd 的比赛。qqq 组询问,每组询问会修改一对球迷和球星间的关系,然后询问举办一场全明星赛,至少要多少个明星才能使所有粉丝都喜欢看。

注意要分清楚a是b的粉丝a喜欢看b的比赛这两个关系,捋清楚之后,就可以发现,对于一个球星和球迷的连通块,其实只需要选其中一个球星,就可以使其中所有球迷来看。

那么就是维护总联通块数 AAA,球星孤点数 BBB,球迷孤点数 CCC 就可以了,答案为 A−BA-BA−B,球星是孤点意味着选他和不选他不影响任何球迷,而如果球迷是孤点那么选哪个球星他都不来看,即如果 C>0C>0C>0,此时答案为 −1-1−1。

连通块用 LCTLCTLCT 维护一下即可,但是要注意这是图而不是树,所以要预处理出每条边被删除的时间,当加入一条新边 (x,y)(x,y)(x,y) 时,假如 x,yx,yx,y 原本就连通,那么看一下他们之间最早被删除的边,假如新加的边删除时间比他晚,那么就把他替换掉。

细节就看代码吧,数据结构题总不能说完所有细节吧qwq:

#include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 400010
#define pb push_back
#define inf 999999999int n,m,q;
struct edge{int x,y,Time;edge(int xx=0,int yy=0,int TIME=inf):x(xx),y(yy),Time(TIME){}bool operator <(const edge &B)const{return x==B.x?y<B.y:x<B.x;}
};
edge get(int x,int y,int Time=inf){if(x>y)swap(x,y);return (edge){x,y,Time};}
vector<edge> e;int St=0;
struct node{edge x;node *zuo,*you,*fa,*mi;int lazy;node(edge X):x(X),zuo(NULL),you(NULL),fa(NULL),mi(this),lazy(0){}void update(int c){lazy^=c;if(c)swap(zuo,you);}void pushdown(){if(zuo)zuo->update(lazy);if(you)you->update(lazy);lazy=0;}bool notroot(){return fa&&(fa->zuo==this||fa->you==this);}void check(){mi=this;if(zuo&&zuo->mi->x.Time<mi->x.Time)mi=zuo->mi;if(you&&you->mi->x.Time<mi->x.Time)mi=you->mi;}
}*d[maxn];
map<edge,node*>mp;
map<edge,int>sta;//status
void rotate(node *x){node *fa=x->fa,*gfa=fa->fa;if(fa->zuo==x){fa->zuo=x->you;if(x->you)x->you->fa=fa;x->you=fa;}else{fa->you=x->zuo;if(x->zuo)x->zuo->fa=fa;x->zuo=fa;}fa->fa=x,x->fa=gfa;if(gfa&&gfa->zuo==fa)gfa->zuo=x;if(gfa&&gfa->you==fa)gfa->you=x;fa->check();x->check();
}
node *zhan[maxn];int t=0;
#define witch(x) (x->fa->zuo==x)
void splay(node *x){node *p=x;zhan[++t]=x;while(p->notroot())zhan[++t]=p=p->fa;while(t)zhan[t--]->pushdown();while(x->notroot()){if(x->fa->notroot()&&witch(x)==witch(x->fa))rotate(x->fa),rotate(x);else rotate(x);}
}
void access(node *x){for(node *y=NULL;x;y=x,x=x->fa)splay(x),x->you=y,x->check();}
void makeroot(node *x){access(x);splay(x);x->update(1);}
node *findrt(node *x){access(x);splay(x);while(x->zuo)x=x->zuo;return x;}
void link(node *x,node *y){makeroot(x);x->fa=y;}
void del(node *x,node *y){makeroot(x);access(y);splay(x);x->you=y->fa=NULL;x->check();}
//以上为LCT板子
int du[maxn],cnt,gu1,gu2;//du记录每个点的度,cnt记录连通块数,gu1,gu2记录球星球迷孤点数
void addedge(int x,int y,int Time){cnt--;if(!du[x]++)if(x<=n)gu1--;else gu2--;if(!du[y]++)if(y<=n)gu1--;else gu2--;edge p=get(x,y,Time);mp[p]=new node(p);link(d[x],mp[p]);link(d[y],mp[p]);
}
void deledge(int x,int y,int Time){cnt++;if(!--du[x])if(x<=n)gu1++;else gu2++;if(!--du[y])if(y<=n)gu1++;else gu2++;edge p=get(x,y,Time);del(d[x],mp[p]);del(d[y],mp[p]);
}
void Link(int x,int y,int Time){node *X=d[x],*Y=d[y];if(findrt(X)==findrt(Y)){makeroot(X);access(Y);splay(X);edge &p=X->mi->x;if(p.Time<Time){sta[p]=2;sta[get(x,y,Time)]=1;deledge(p.x,p.y,p.Time),addedge(x,y,Time);}else sta[get(x,y,Time)]=2;}else addedge(x,y,Time),sta[get(x,y,Time)]=1;
}int main()
{scanf("%d %d %d",&n,&m,&q);cnt=(gu1=n)+(gu2=m);for(int i=1;i<=n+m;i++)d[i]=new node((edge){0,0,inf});for(int i=1;i<=n;i++){int k,x;scanf("%d",&k);St+=k;while(k--)scanf("%d",&x),x+=n,e.pb(get(i,x)),sta[e.back()]=e.size()-1;}for(int i=1,x,y;i<=q;i++){scanf("%d %d",&x,&y);x+=n;edge p=get(x,y);e.pb(p);if(!sta.count(p)||sta[p]==-1)sta[p]=e.size()-1;else e[sta[p]].Time=e.back().Time=i,sta[p]=-1;}sta.clear();for(int i=0;i<St;i++)Link(e[i].x,e[i].y,e[i].Time);for(int i=St;i<St+q;i++){if(!sta.count(e[i])||sta[e[i]]==0){sta[e[i]]=1,Link(e[i].x,e[i].y,e[i].Time);}else{if(sta[e[i]]==1)deledge(e[i].x,e[i].y,e[i].Time);sta[e[i]]=0;}if(gu2)printf("-1\n");else printf("%d\n",cnt-gu1);}
}

C. Cinema

有一个 n×mn\times mn×m 的网格图,你要在上面放最少的人,使得任意两人不相邻,且不能放更多的人,给出一个方案。

mmm 只有 151515 容易想到状压,理所当然地用 000 表示没人 111 表示有人然后行与行之间转移。

然后发现没有办法判断是否还剩下可以放的位置……这种 dp\text{dp}dp 只能做最多人做不了最少人。

于是需要加一维状态,容易发现最后的图中,对于任意位置,不是有人就是旁边有人,为了方便转移,令 000 表示这个位置下面有人,111 表示这个位置的上、左、右至少有一个人,222 表示这个位置有人。

先 dfsdfsdfs 找到所有合法状态,不合法状态满足:存在 222222 或 000000,111111 的两边没有 222(这意味着上面要放两个连续的 222)。

对于每个状态,再找到它下一行的合法状态,下一行的状态要在上面的限制的基础上再满足一些限制:222 下面必须是 111,000 下面必须是 222,111 的上、左、右至少有一个 222。

然后大力 dp\text{dp}dp 就可以了,题解搜出来的状态是六七千个,但是我搜出来的状态有一万四,可能是有什么定义不够优秀吧……当时本地跑需要 7s7s7s 左右,临走前自信交了一发居然过了,挺突然的……

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define pb push_backstruct par{int x,pos;bool operator <(const par &B)const{return x<B.x;}
};
vector<par> q[20];
vector<int> sta;
int id[13000010],idtot;
void print(int x,int m);
void dfs(int x,int now,int m){if(x==m){id[now]=idtot++;sta.pb(now);return;}if(x==0)for(int i=2;i>=0;i--)dfs(x+1,i,m);else if(now%3!=1)dfs(x+1,now*3+1,m);//0,2 -> 1else{dfs(x+1,now*3+2,m);//1 -> 2if(x==2&&now%3==1&&now/3%3==1)return;if(x>2&&now%3==1&&now/3%3==1&&now/9%3==1)return;dfs(x+1,now*3+0,m);//1 -> 0,1dfs(x+1,now*3+1,m);}
}
int thi[20];
vector<int> ne[14010];
void dfs2(vector<int> &vec,int st,int x,int now,int m){if(x==m){for(int i=0;i<m;i++)if(now/thi[i]%3==1&&st/thi[i]%3!=2&&(i==0||now/thi[i-1]%3!=2)&&(i==m-1||now/thi[i+1]%3!=2))return;vec.pb(id[now]);return;}int p=st/thi[m-x-1]%3;if(x==0){if(p==1)dfs2(vec,st,x+1,now*3+0,m);if(p!=0)dfs2(vec,st,x+1,now*3+1,m);if(p!=2)dfs2(vec,st,x+1,now*3+2,m);}else if(now%3!=1){if(p!=0)dfs2(vec,st,x+1,now*3+1,m);}else{if(p!=2)dfs2(vec,st,x+1,now*3+2,m);if(x==2&&now%3==1&&now/3%3==1)return;if(x>2&&now%3==1&&now/3%3==1&&now/9%3==1)return;if(p==1)dfs2(vec,st,x+1,now*3+0,m);if(p!=0)dfs2(vec,st,x+1,now*3+1,m);}
}
int f[1010][14010],fa[1010][14010],two[14010];
int count0(int x,int m){int re=0;for(int i=0;i<m;i++)if(x/thi[i]%3==0)re++;return re;
}
int count2(int x,int m){int re=0;for(int i=0;i<m;i++)if(x/thi[i]%3==2)re++;return re;
}
bool SetFirst(int x,int m){for(int i=0;i<m;i++)if(x/thi[i]%3==1&&(i==0||x/thi[i-1]%3!=2)&&(i==m-1||x/thi[i+1]%3!=2))return false;return true;
}
vector<int> ed,ans[1010];
void print(int p,int m){for(int i=0;i<m;i++){if(p/thi[i]%3==0)printf("0");if(p/thi[i]%3==1)printf("1");if(p/thi[i]%3==2)printf("2");}
}
void work(int m){sta.clear();idtot=0;dfs(0,0,m);for(int i=0;i<idtot;i++){ne[i].clear(),dfs2(ne[i],sta[i],0,0,m);}const int N=1000;for(int i=0;i<=N;i++)for(int j=0;j<idtot;j++)f[i][j]=1e9,fa[i][j]=-1;ed.clear();for(int j=0;j<idtot;j++){two[j]=count2(sta[j],m);if(SetFirst(sta[j],m))f[1][j]=two[j];if(!count0(sta[j],m))ed.pb(j);}sort(q[m].begin(),q[m].end());int st=0;for(int i=1;i<=N;i++){while(st<q[m].size()&&q[m][st].x==i){int pos=q[m][st].pos,now=i,k=ed[0];for(int j:ed)if(f[i][j]<f[i][k])k=j;ans[pos].pb(f[i][k]);while(now>0)ans[pos].pb(sta[k]),k=fa[now--][k];st++;}if(i<N)for(int j=0;j<idtot;j++){for(int k:ne[j]){if(f[i+1][k]>f[i][j]+two[k]){f[i+1][k]=f[i][j]+two[k];fa[i+1][k]=j;}}}}
}
int T;
struct que{int n,m;}Q[1010];int main()
{scanf("%d",&T);for(int _=1;_<=T;_++){scanf("%d %d",&Q[_].n,&Q[_].m);q[Q[_].m].pb((par){Q[_].n,_});}thi[0]=1;for(int i=1;i<15;i++)thi[i]=3*thi[i-1];for(int i=1;i<=15;i++)work(i);for(int _=1;_<=T;_++){int m=Q[_].m;printf("Case #%d: %d\n",_,ans[_][0]);while(Q[_].n--){int p=ans[_].back();ans[_].pop_back();for(int i=0;i<m;i++){if(p/thi[i]%3==0)printf(".");//这里写的这么烦是因为调试的时候搞得,后来懒得改了……if(p/thi[i]%3==1)printf(".");if(p/thi[i]%3==2)printf("*");}printf("\n");}}
}

E. Enigmatic Partition

nnn 的一个优秀的整数拆分满足:ai≤ai+1≤ai+1,am=a1+2a_i\leq a_{i+1}\leq a_i+1,a_m=a_1+2ai​≤ai+1​≤ai​+1,am​=a1​+2,令 f(k)f(k)f(k) 表示 kkk 的优秀整数拆分数量,求 ∑i=lrf(i)\sum_{i=l}^r f(i)∑i=lr​f(i)。

一个优秀的整数分拆肯定由 d,d+1,d+2d,d+1,d+2d,d+1,d+2 三个数组成,相当于先往 mmm 个位置上全部放 ddd,然后使一个后缀 +1+1+1,再使另一个后缀 +1+1+1,这两个后缀不能相同。

考虑一个暴力:令 N=105N=10^5N=105,枚举 a1a_1a1​,枚举序列长度 mmm,再枚举 kkk 表示两次后缀加一一共加了多少,可以发现 kkk 的枚举范围只能到 2m2m2m,mmm 的枚举范围只到 ⌊Na1⌋\lfloor \frac N {a_1} \rfloor⌊a1​N​⌋,所以时间复杂度为:
∑i=1N⌊Ni⌋2\sum_{i=1}^N \lfloor \frac N i \rfloor^2 i=1∑N​⌊iN​⌋2

在 iii 比较小时时间复杂度会很高,但此时我们可以用完全背包来做,于是时间复杂度就不高了。

代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100010
#define ll long longint T,l,r;
ll f[maxn],sum[maxn];
void work(){int N=100000,block=100;for(int j=1;j<block;j++){memset(f,0,sizeof(f));f[j+j+1+j+2]=1;for(int k=0;k<=2;k++){for(int i=3*j+3;i<=N;i++)if(i+j+k<=N)f[i+j+k]+=f[i];}for(int i=3*j+3;i<=N;i++)sum[i]+=f[i];}for(int i=block;i<=N;i++){//a[1] for(int j=3;i*j<=N;j++){//len for(int k=3;k<2*(j-1)&&i*j+k<=N;k++){if(k>j-1)sum[i*j+k]+=(2*(j-1)-k+1)/2;else sum[i*j+k]+=(k-1)/2;}}}for(int i=1;i<=N;i++)sum[i]+=sum[i-1];
}int main()
{work();scanf("%d",&T);for(int _=1;_<=T;_++)scanf("%d %d",&l,&r),printf("Case #%d: %lld\n",_,sum[r]-sum[l-1]);
}

还有另一个更优秀的做法,时间复杂度严格 O(nln⁡n)O(n\ln n)O(nlnn) 而且常数不超过 222,在这里。

大致的思想是:先对 fff 做差分,枚举 a1a_1a1​ 和 mmm,发现从 a1×m+3a_1\times m+3a1​×m+3 开始每隔一位 fff 会 +1+1+1,然后从 (a1+1)×m+1(a_1+1)\times m+1(a1​+1)×m+1 开始又会连续减一,但是又发现从 (a1+2)×m−3(a_1+2)\times m-3(a1​+2)×m−3 (包括这一位)往前每隔一位就会同时出现 +1+1+1 和 −1-1−1 可以抵消掉,那么剩下的 +1,−1+1,-1+1,−1 就都是隔一位出现的了,再做一次二阶差分,那么每次修改就可以 O(1)O(1)O(1) 实现。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 300010int T,l,r;
long long f[maxn];int main()
{for(int i=1;i<=100000;i++){for(int j=1;i*j<=100000;j++){f[i*j+3]++;f[i*j+3+j-2]--;f[i*j+3+j-2+1]--;f[(i+2)*j-3+3]++;}}for(int i=2;i<=100000;i++)f[i]+=f[i-2];for(int i=1;i<=100000;i++)f[i]+=f[i-1];for(int i=1;i<=100000;i++)f[i]+=f[i-1];scanf("%d",&T);for(int _=1;_<=T;_++)scanf("%d %d",&l,&r),printf("Case #%d: %lld\n",_,f[r]-f[l-1]);
}

G. Game SET

有 nnn 张牌,每张牌有四个属性,三张牌能够组成SET当且仅当每个属性都满足:三张牌的该属性都相同或都不同。找出一个SET。

枚举前两张,用 map\text{map}map 判对应的第三张是否存在即可,时间复杂度 O(n2log⁡n)O(n^2\log n)O(n2logn)。

代码如下:

#include <cstdio>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define pb push_backint T,n;
struct card{int val[4];bool operator <(const card &B)const{return val[0]==B.val[0]?(val[1]==B.val[1]?(val[2]==B.val[2]?val[3]<B.val[3]:val[2]<B.val[2]):val[1]<B.val[1]):val[0]<B.val[0];}
}a[310];
char s[110];
string p;
string find(int st){string re;re.clear();for(int i=st+1;s[i]!=']';i++)re+=s[i];return re;
}
map<card,int> mp;
vector<int> C[4];int main()
{scanf("%d",&T);for(int Test=1;Test<=T;Test++){scanf("%d",&n);mp.clear();for(int i=1;i<=n;i++){scanf("%s",s+1);int st=1;p=find(st);if(p=="*")a[i].val[0]=0;if(p=="one")a[i].val[0]=1;if(p=="two")a[i].val[0]=2;if(p=="three")a[i].val[0]=3;st+=p.length()+2;p=find(st);if(p=="*")a[i].val[1]=0;if(p=="diamond")a[i].val[1]=1;if(p=="squiggle")a[i].val[1]=2;if(p=="oval")a[i].val[1]=3;st+=p.length()+2;p=find(st);if(p=="*")a[i].val[2]=0;if(p=="open")a[i].val[2]=1;if(p=="solid")a[i].val[2]=2;if(p=="striped")a[i].val[2]=3;st+=p.length()+2;p=find(st);if(p=="*")a[i].val[3]=0;if(p=="red")a[i].val[3]=1;if(p=="green")a[i].val[3]=2;if(p=="purple")a[i].val[3]=3;mp[a[i]]=i;}printf("Case #%d: ",Test);bool ans=false;for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++){for(int k=0;k<4;k++){C[k].clear();C[k].pb(0);if(a[i].val[k]==0||a[j].val[k]==0)for(int p=1;p<4;p++)C[k].pb(p);else if(a[i].val[k]==a[j].val[k])C[k].pb(a[i].val[k]);else C[k].pb(6-a[i].val[k]-a[j].val[k]);}for(int c0:C[0])for(int c1:C[1])for(int c2:C[2])for(int c3:C[3]){card now=(card){c0,c1,c2,c3};if(!ans&&mp.count(now)&&i!=mp[now]&&j!=mp[now]){ans=true;printf("%d %d %d\n",i,j,mp[now]);}}if(ans)break;}if(ans)break;}if(!ans)printf("-1\n");}
}

题解用到一个结论:212121 张牌内必定存在SET,所以可以 O(213)O(21^3)O(213) 直接暴力……

H. hard String Problem

给出 nnn 个字符串,将他们以自己为循环节无限循环,然后问这些字符串有多少个相同的子串。

先将所有字符串变成自己的最小循环节,然后就有一个结论:两个无限循环字符串的公共子串长度不超过长串长度的三倍,证明参照题解:

于是可以考虑让所有字符串去和最短的那个匹配,那么就只需要将长度变成原来的四倍就够了,而不需要无限长,而最短的子串长度要翻倍到最长子串的四倍长度。

还要判断无限解:假如所有字符串本质相同,那么就有无限个解。

题解代码中有很多细节值得学习:

  1. 求自身的最短循环节可以用kmp,假设字符串由 ppp 个最短循环节组成,那么 (p−1)(p-1)(p−1) 个循环节组成的字符串一定是原串的一个border(相同前后缀),用kmp可以找;
  2. 判断两个字符串是否本质相同:如果是,那么其中一个旋转若干次之后一定能得到另一个,那么对于所有字符串,将其旋转成字典序最小的形式,然后直接去重;
  3. 求解时需要对最短的子串建SAM,然后找有多少个子串在所有字符串中都有出现。实现的时候将其他字符串丢到这个SAM上跑,将出现过的子串全部标记,最后看哪些子串恰好每次都被标记过即可,这样暴力标记复杂度其实是 O(N2N)O(N\sqrt {2N})O(N2N​) 的,证明参考这里。

代码如下:

#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 2000010
#define ll long long
#define pb push_backint n;
char cs[maxn];
string S[maxn];
namespace KMP{int len;char st[maxn];int next[maxn];void init(const char *s){len=strlen(s+1);memcpy(st,s,(len+2)*sizeof(char));next[0]=-1;next[1]=0;for(int i=1;i<len;i++){int j=next[i];while(j!=-1&&st[j+1]!=st[i+1])j=next[j];next[i+1]=j+1;}}int solve(){int j=next[len];while(j!=-1){if(len%(len-j)==0)return len-j;j=next[j];}}
}
int min_pos(char *s){//这个是找:将s旋转成字典序最小的形式,需要将前多少位放到后面int len=strlen(s);int i=0,j=1,k=0;while(i<len&&j<len&&k<len){//这部分可以这样理解:用i记录当前字典序最小的开头位置,j往后尝试找字典序更小的位置,找到了的话i就跳到j那里int p=s[(i+k)%len]-s[(j+k)%len];if(p==0)k++;else{if(p>0)i+=k+1;else j+=k+1;if(i==j)j++;k=0;}}return i;
}
string handle(char *s){KMP::init(s);int len=KMP::solve();s[len+1]=0;int pos=min_pos(s+1);for(int i=1;i<=pos;i++)s[i+len]=s[i];s[len+pos+1]=0;return string(s+pos+1);
}
void chkmin(int &x,int y){if(y<x)x=y;}
void chkmax(int &x,int y){if(y>x)x=y;}
namespace SAM{struct state{int ne[26],len,link;}st[maxn<<1];int now,p,q,id=0,last=0;int v[maxn<<1],num=0;int mat_max[maxn<<1],match_len[maxn<<1],match_times[maxn<<1];void init(){st[0].link=-1;memset(v,0,sizeof(v));memset(match_len,63,sizeof(match_len));}void extend(int x){now=++id;st[now].len=st[last].len+1;for(p=last;p!=-1&&!st[p].ne[x];p=st[p].link)st[p].ne[x]=now;if(p!=-1){q=st[p].ne[x];if(st[p].len+1==st[q].len)st[now].link=q;else{int clone=++id;st[clone]=st[q];st[clone].len=st[p].len+1;for(;p!=-1&&st[p].ne[x]==q;p=st[p].link)st[p].ne[x]=clone;st[q].link=st[now].link=clone;}}last=now;}struct par{int x,len;};vector<par> nodes;vector<int> a,b;bool cmp(int x,int y){return st[x].len>st[y].len;}void solve(string &s){nodes.clear();a.clear();b.clear();int now=0,len=0,n=s.length();for(int i=0;i<n;i++){int p=s[i]-'a';while(now!=0&&!st[now].ne[p])now=st[now].link,len=st[now].len;if(st[now].ne[p]){now=st[now].ne[p];len++;}nodes.pb((par){now,len});a.pb(now);}sort(a.begin(),a.end(),cmp);num++;for(int x:a){int i=x;while(i!=0&&v[i]!=num){b.pb(i);mat_max[i]=0;v[i]=num;i=st[i].link;}}for(par i:nodes)chkmax(mat_max[i.x],i.len);sort(b.begin(),b.end(),cmp);for(int i:b){chkmax(mat_max[st[i].link],st[st[i].link].len);chkmin(match_len[i],mat_max[i]);match_times[i]++;}}ll get_ans(int tot_times){ll re=0;for(int i=1;i<=id;i++)if(match_times[i]==tot_times){re+=max(0,match_len[i]-st[st[i].link].len);}return re;}
}int main()
{ios::sync_with_stdio(false);cin>>n;for(int i=1;i<=n;i++){cin>>cs+1;S[i]=handle(cs);}sort(S+1,S+n+1);n=unique(S+1,S+n+1)-S-1;if(n==1){cout<<-1;return 0;}int min_len=1e9,max_len=0;for(int i=1;i<=n;i++){chkmin(min_len,S[i].length());chkmax(max_len,S[i].length());}SAM::init();for(int i=1;i<=n;i++)if(S[i].length()==min_len){for(int j=min_len;j<=max_len*4;j+=min_len)for(int k=0;k<min_len;k++)SAM::extend(S[i][k]-'a');min_len=i;break;}for(int i=1;i<=n;i++)if(i!=min_len){S[i]=S[i]+S[i];S[i]=S[i]+S[i];//变成原来的四倍长度SAM::solve(S[i]);}cout<<SAM::get_ans(n-1);return 0;
}

I. Interesting Computer Game

有 nnn 组数字,每组两个数,你可以从每组中选一个数,问你最多能选出多少个不同的数。

这题看起来酷似网络流,但是你硬跑的话大概是会T飞的。

考虑每组内的两个数连边,那么对于一个连通块,如果边数为点数减一(即是棵树),那么能拿的点只有 n−1n-1n−1 个,否则总是能拿完每个点,策略大概就是每次取叶子,有环的话怎么取都行。

代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200010
#define pb push_backint T,n,a[maxn][2];
vector<int> b;
int fa[maxn],v[maxn],c[maxn];
int findfa(int x){return x==fa[x]?x:fa[x]=findfa(fa[x]);}int main()
{scanf("%d",&T);for(int _=1;_<=T;_++){scanf("%d",&n);b.clear();for(int i=1;i<=n;i++){scanf("%d %d",&a[i][0],&a[i][1]);b.pb(a[i][0]);b.pb(a[i][1]);}sort(b.begin(),b.end());b.erase(unique(b.begin(),b.end()),b.end());for(int i=1;i<=2*n;i++)fa[i]=i,v[i]=0;for(int i=1;i<=n;i++){int x=lower_bound(b.begin(),b.end(),a[i][0])-b.begin()+1;int y=lower_bound(b.begin(),b.end(),a[i][1])-b.begin()+1;c[x]=c[y]=1;x=findfa(x),y=findfa(y);if(x!=y)fa[y]=x,v[x]|=v[y];else v[x]=1;}int ans=0;for(int i=1;i<=2*n;i++)if(c[i]){ans++;if(fa[i]==i&&!v[i])ans--;}printf("Case #%d: %d\n",_,ans);}
}

J. Jumping Points

给你 nnn 条线段,第 iii 条线段的坐标为 (x,li(x,l_i(x,li​ ~ ri)r_i)ri​),你要在每一条线段上选一个点,令 dis(i,i+1)dis(i,i+1)dis(i,i+1) 表示第 iii 个点到第 i+1i+1i+1 个点的距离,要求最小化 ∑i=1n−1dis(i,i+1)\sum_{i=1}^{n-1} dis(i,i+1)∑i=1n−1​dis(i,i+1)。

先考虑固定起点和终点的情况,那么此时相当于要从起点拉一条线到终点,为了让距离最小,线应该是紧绷的,就像这样:

可以发现,折线的拐点只可能在线段的端点上,所以可以考虑贪心。

对于当前位置,他的后面的线段会限制他的视野,找到视野外的最近的线段,为了到达让这条线段在视野内,肯定要贪心地走到限制了当前位置看不到那条视野外的线段的端点上。

比如说上面那副图,假设当前点是起点,那么第一条在视野外的线段为下面的红色线段:

其中,限制了当前位置看不到那条视野外的线段的端点是紫色节点,所以下一步肯定走到紫色节点上。

这样贪心可能是 n2n^2n2 的,可以考虑维护出以当前点为最左端的凸包,维护一个以每条线段的上端点形成的下凸包,维护一个以每条线段下端点形成的上凸包,那么两个凸包大概就形成了一个喇叭的样子,这喇叭最左端是当前位置,往右一位的两个点分别限制了视野的下方和上方。

这是固定了起点和终点的情况,事实上我们只需要求出 最左边的线段的上下端点+最右边的线段的上下端点 四种组合作为起点终点的情况的解就好了。

还有一些细节就看代码吧,全部讲完就太长了:

#include <cstdio>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 100010
#define pb push_back
#define eps 1e-7int T,n;
struct point{int x;double y;point(int xx=0,double yy=0):x(xx),y(yy){}
};
int l[maxn],r[maxn];
vector<double> work(int Sy,int Ty){vector<double> re;re.clear();re.pb(Sy);int L1=l[1],R1=r[1],Ln=l[n],Rn=r[n];l[1]=r[1]=Sy;l[n]=r[n]=Ty;static point ql[maxn],qr[maxn];//维护上下凸包的两个单调队列static int stl,edl,str,edr;ql[stl=edl=1]=point(1,Sy);qr[str=edr=1]=point(1,Sy);int nowx=1,nowy=Sy,next=2;while(nowx<n){int nex=n,ney=l[n];while(next<=n){if(stl<edl && 1ll*(r[next]-ql[stl].y)*(ql[stl+1].x-ql[stl].x)<1ll*(ql[stl+1].y-ql[stl].y)*(next-ql[stl].x)){//next线段位于视野下方nex=ql[stl+1].x;ney=ql[stl+1].y;stl++;qr[str=edr=1]=point(nex,ney);break;}if(str<edr && 1ll*(l[next]-qr[str].y)*(qr[str+1].x-qr[str].x)>1ll*(qr[str+1].y-qr[str].y)*(next-qr[str].x)){//next线段位于视野上方nex=qr[str+1].x;ney=qr[str+1].y;str++;ql[stl=edl=1]=point(nex,ney);break;}while(stl<edl && 1ll*(l[next]-ql[edl].y)*(ql[edl].x-ql[edl-1].x)>=1ll*(ql[edl].y-ql[edl-1].y)*(next-ql[edl].x))edl--;//上凸包,斜率不允许增ql[++edl]=point(next,l[next]);while(str<edr && 1ll*(r[next]-qr[edr].y)*(qr[edr].x-qr[edr-1].x)<=1ll*(qr[edr].y-qr[edr-1].y)*(next-qr[edr].x))edr--;//下凸包,斜率不允许减qr[++edr]=point(next,r[next]);next++;}for(int i=nowx+1;i<=nex;i++){//从(nowx,nowy)走到(nex,ney),路径是沿直线走re.pb(1.0*(ney-nowy)/(nex-nowx)*(i-nowx)+nowy);}nowx=nex,nowy=ney;}l[1]=L1,r[1]=R1,l[n]=Ln,r[n]=Rn;int ml=0,mr=1e9;for(int i=1;i<=n;i++){//处理特殊情况,开头的若干个点可能不需要取线段的端点ml=max(ml,l[i]);mr=min(mr,r[i]);if(re[i-1]<ml-eps||re[i-1]>mr+eps){for(int j=i-2;j>=0;j--)re[j]=re[i-2];break;}}ml=0,mr=1e9;for(int i=n;i>=1;i--){//结尾的若干个点也可能不需要取线段端点ml=max(ml,l[i]);mr=min(mr,r[i]);if(re[i-1]<ml-eps||re[i-1]>mr+eps){for(int j=i;j<n;j++)re[j]=re[i];break;}}return re;
}
double calc(vector<double> pos){double re=0;for(int i=1;i<pos.size();i++){re+=sqrt(1+(pos[i]-pos[i-1])*(pos[i]-pos[i-1]));}return re;
}int main()
{scanf("%d",&T);for(int _=1;_<=T;_++){scanf("%d",&n);int ml=0,mr=1e9;for(int i=1;i<=n;i++){scanf("%d %d",&l[i],&r[i]);ml=max(ml,l[i]),mr=min(mr,r[i]);}printf("Case #%d:\n",_);if(ml<=mr){for(int i=1;i<=n;i++)printf("%d %d\n",i,ml);continue;}vector<double> ans1=work(l[1],l[n]);vector<double> ans2=work(l[1],r[n]);vector<double> ans3=work(r[1],l[n]);vector<double> ans4=work(r[1],r[n]);vector<double> ans=ans1;double val=calc(ans),tmp;if((tmp=calc(ans2))<val)ans=ans2,val=tmp;if((tmp=calc(ans3))<val)ans=ans3,val=tmp;if((tmp=calc(ans4))<val)ans=ans4,val=tmp;for(int i=1;i<=n;i++)printf("%d %.6lf\n",i,ans[i-1]);}
}

K. Kabaleo Lite

有一家餐厅,第 iii 道菜收益为 aia_iai​,有 bib_ibi​ 碟,给每个客人的菜一定是一个前缀,并且前缀内每道菜各给一道,问招待最多客人的前提下最大收益是多少。

答案会爆 long long\text{long~long}long long 确实被阴到了……刚好就爆一点……

贪心,每次取贡献最大的前缀即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010
#define ll long longint T,n,a[maxn],b[maxn],mi[maxn];
struct prefix{int x;ll sum;}s[maxn];
bool cmp(prefix x,prefix y){return x.sum>y.sum;}
char op[maxn];int t=0;
void write(__int128 x){if(x<0)putchar('-'),x=-x;if(x==0)putchar('0');while(x)op[++t]=x%10+'0',x/=10;while(t)putchar(op[t--]);
}int main()
{scanf("%d",&T);for(int Test=1;Test<=T;Test++){scanf("%d",&n);s[0].sum=0;mi[0]=1e9;for(int i=1;i<=n;i++){scanf("%d",&a[i]);s[i]=(prefix){i,s[i-1].sum+a[i]};}for(int i=1;i<=n;i++)scanf("%d",&b[i]),mi[i]=min(mi[i-1],b[i]);sort(s+1,s+n+1,cmp);int guest=0;__int128 ans=0;for(int i=1;i<=n;i++){int x=s[i].x;ll sum=s[i].sum;if(mi[x]<=guest)continue;ans+=(__int128)sum*(mi[x]-guest);guest=mi[x];}printf("Case #%d: %d ",Test,guest);write(ans);printf("\n");}
}

2020牛客暑期多校训练营(第八场)题解相关推荐

  1. 2020牛客暑期多校训练营(第一场)

    文章目录 A B-Suffix Array B Infinite Tree C Domino D Quadratic Form E Counting Spanning Trees F Infinite ...

  2. 2020牛客暑期多校训练营(第二场)

    2020牛客暑期多校训练营(第二场) 最烦英语题 文章目录 A All with Pairs B Boundary C Cover the Tree D Duration E Exclusive OR ...

  3. E Groundhog Chasing Death(2020牛客暑期多校训练营(第九场))(思维+费马小定理+质因子分解)

    E Groundhog Chasing Death(2020牛客暑期多校训练营(第九场))(思维+费马小定理+质因子分解) 链接:https://ac.nowcoder.com/acm/contest ...

  4. 2020牛客暑期多校训练营(第一场)A B-Suffix Array(后缀数组,思维)

    链接:https://ac.nowcoder.com/acm/contest/5666/A 来源:牛客网 题目描述 The BBB-function B(t1t2-tk)=b1b2-bkB(t_1 t ...

  5. 2020牛客暑期多校训练营(第二场)Just Shuffle

    https://ac.nowcoder.com/acm/contest/5667/J 题目大意:给你一个置换A,使得置换P^k=A,让你求出置换P. 思路:我们根据置换A再置换z次,那么就等于置换p ...

  6. 2020牛客暑期多校训练营(第一场)j-Easy Integration(思维,分数取模,沃斯利积分)

    题目链接 题意: 给你一个积分公式,给你一个n,问积分公式的值取模后的结果. 思路: 积分公式(沃利斯积分)值的结论直接就是(n!)^2/(2n+1)!,求个阶乘,再用费马小定理给1/(2n+1)!取 ...

  7. 2020 牛客暑期多校训练营(第一场)F

    题目大意: 多次输入两个a,b字符串他们可以无限次的重复变成aaa,或者bbb 比较他们的大小,相同输出 =,a<b输出 <,a>b输出 >. 输入: aa b zzz zz ...

  8. 2020牛客暑期多校训练营(第二场)未完待续......

    F. Fake Maxpooling 题目: 题目大意: 输入n,m,k.矩阵的尺寸为nm,其中每一个元素为A[i][j] = lcm( i , j ).从中找出所有kk的子矩阵中元素最大的数之和. ...

  9. 2020牛客暑期多校训练营(第一场)J、Easy Integration (数学、分部积分)

    题目链接 题面: 题意: 求给定的定积分. 题解,化成 ∫ xn (1-x)n dx 然后用分部积分法即可得. 分部积分法:∫ udv = uv - ∫ vdu 最终为 n!/((n+1)*(n+2) ...

  10. 2020牛客暑期多校训练营(第二场)题解

    废话 蒟蒻不会积分,K不会做. 文章目录 废话 A. All with Pairs B. Boundary C. Cover the Tree D. Duration E. Exclusive OR ...

最新文章

  1. hashmap的get查找过程
  2. GitHub Alibaba Group 下 Star 最多的开源项目是?
  3. 【Java中级】(三)IO
  4. 新建文件注释_PDF汇总注释原来如此简单
  5. oracle 表空间 碎片,Oracle表空间碎片整理
  6. InfoPath 发布表单到SharePoint库报错
  7. Chrome 扩展工具及命令
  8. 编程时遇到问题的解决方向
  9. 打磨TF卡叠加SIM的注意问题
  10. 什么软件可以测试电信网速,测试网速的简单的三种方法
  11. SPSS的下载和使用经历
  12. 图解积分法_计算机模拟图解积分法求气相吸收总传质单元数
  13. 小程序源码:游戏助手王者战力查询扫码登录多功能微信小程序
  14. 网络攻击与防御基本概念
  15. 四大机器学习降维方法
  16. python挖矿木马_记一次阿里云被植入挖矿木马的事件
  17. win7系统如何关闭安全模式,关闭安全模式的方法
  18. UBOOT I2C读写详解(基于mini2440)
  19. 用c语言写一个唐诗的程序,文言文编程95后又出新作,在287051行古诗中找出了“唐诗幻方”!...
  20. 局域网监控、网络监控软件之交换机端口镜像配置

热门文章

  1. 时代周刊:谷歌微软争夺高校电子邮件外包市场
  2. 如何批量图片重命名不同名字?
  3. Java求1-100的质数和
  4. 幽默笑话,来不及脱裤子,木子家原创
  5. 成功解决LINK : fatal error LNK1181: 无法打开输入文件“avdevice.lib” error: command 'D:\\Program Files (x86)\\Micr
  6. Canvas 贪吃蛇大作战
  7. 记录-Selection.addRange() 已弃用,该如何解决
  8. scilab和matlab的区别,Fortran, Matlab, Octave, Scilab计算速度比较
  9. 阿里云短信服务报错:SignatureDoesNotMatch : Specified signature is not matched with our calculation.
  10. yarn computed integrity doesn‘t match our records 错误