【Codeforces】1129 Round #542 [Alex Lopashev Thanks-Round] (Div. 1) A-E题解

版权声明:欢迎转载(请附带原链接)ヾ(๑╹◡╹)ノ https://blog.csdn.net/corsica6/article/details/87915132

传送门:CF1129

手速变慢了…代码熟练度不够


A1&2. Toy Train

g [ x ] g[x] 存起点为 a i a_i 的所有 b i b_i (按 d i s ( a i , b i ) dis(a_i,b_i) 排序)。

m x = m a x ( g [ x ] . s i z e ( ) ) mx=max(g[x].size()) ,则对于任意起点,前 m x 1 mx-1 圈一定是要走的,只是最后一圈不一样。,枚举起点 O ( n 2 ) O(n^2) 跑一遍最后一圈即可。(有一些细节)

#include<bits/stdc++.h>
#define mkp make_pair
#define fi first
#define sc second
#define pii pair<int,int>
#define pb push_back
using namespace std;
typedef long long ll;
typedef double db;
const int N=20005;

int n,m,cnt,mx,cot,pos[5010];ll ans,res;
vector<int>g[5010],rep[5010];
int del[5010];

inline int dis(int x,int y)
{
	if(y>=x) return y-x;
	return y-x+n;
}

inline int add(int x,int y)
{
	x+=y;return x>n?x-n:x;
}

inline void cal(int st)
{
	int i,nw=0;cot=0;ll bs=0;
	memset(del,0,sizeof(del));
	for(i=1;i<=n;++i){
		rep[i].clear();pos[i]=0;
		if(g[i].size()+1<mx) continue;
		if(g[i].size()==mx){
			if(mx>1 && dis(st,i)>dis(st,add(i,g[i][1])) && add(i,g[i][1])!=st){
				nw++;del[add(i,g[i][1])]++;
			}
		    rep[i].pb(g[i][0]),cot++;
	    }else{
	    	if(mx>1 && dis(st,i)>dis(st,add(i,g[i][0])) && add(i,g[i][0])!=st)
			nw++,del[add(i,g[i][0])]++;
		}
	}
	for(i=st;;i= i==n?1:(i+1)){
		if(pos[i]<rep[i].size()){
			nw++;del[add(i,rep[i][pos[i]])]++;
			pos[i]++;cot--;
		}
		nw-=del[i];del[i]=0;
		if((!nw)&&(!cot)){
			printf("%I64d ",bs+ans);return;
		}
		bs++;
	}
}

int main(){
	int i,j,a,b;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;++i){
		scanf("%d%d",&a,&b);
		g[a].pb(dis(a,b));
	}
	for(i=1;i<=n;++i) sort(g[i].begin(),g[i].end());
	mx=0;
	for(i=1;i<=n;++i) mx=max(mx,(int)g[i].size());
	ans+=(ll)(mx-1)*n;
	for(int x=1;x<=n;++x) cal(x);
	return 0;
}

B.Wrong Answer

这题想了很久,因为没法证明构造是对的,然而AC了…

考虑构造一个长度为 2000 2000 的答案,分成三部分:
一段前缀0+一个’-1’+全为正的后缀(设和为 S S ,长度为 l l )。

则Alice的答案是 S l S·l ,实际答案为 ( S 1 ) n (S-1)n

S n n S l = k S ( n l ) = k + n Sn-n-Sl=k\to S(n-l)=k+n

枚举 l l 判断即可。

正确性玄学?


C.Morse Code

构造 S A M SAM ,对于每个结点维护 t i m i tim_i 表示该结点right集合中最前的位置, d p i dp_i 表示从根走到当前结点的所有字符串能构成的摩斯密码的方案数。

拓扑排序 d p dp ,每次 O ( 2 4 ) O(2^4) 暴力转移。

#include<bits/stdc++.h>
#define mkp make_pair
#define fi first
#define sc second
#define pii pair<int,int>
#define pb push_back
using namespace std;
typedef long long ll;
typedef double db;
const int N=3005,mod=1e9+7;

int n,cnt=1,last,cur=1,dis[N<<1];
int dp[N<<1],tim[N<<1],ans[N];
ll sum;

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void dc(int &x,int y){x-=y;if(x<0) x+=mod;}
int ch[N<<1][2];

struct bk{
    int fa,size;
}t[N<<1];

inline void insert(int a,int d)
{
    last=cur;cur=++cnt;dis[cur]=d;tim[cur]=d;
    int p=last;
    for(;p && !ch[p][a];p=t[p].fa) ch[p][a]=cur;
    if(!p) t[cur].fa=1;else{
        int q=ch[p][a];
        if(dis[q]==dis[p]+1) t[cur].fa=q;else{
            int nt=++cnt;tim[cnt]=tim[q];
			dis[nt]=dis[p]+1;
            memcpy(ch[nt],ch[q],sizeof(ch[q]));
            t[nt].fa=t[q].fa;t[q].fa=nt;t[cur].fa=nt;
            for(;ch[p][a]==q;p=t[p].fa) ch[p][a]=nt;
        }
    }
    t[cur].size=1;
}

int dd[N<<1];
queue<int>que;

int main(){
	int i,j,k,a,b,c,i1,i2,i3,i4,j1,j2,j3,j4;
	scanf("%d",&n);
	for(i=1;i<=n;++i){
		scanf("%d",&a);
		insert(a,i);
	}
	for(i=1;i<=cnt;++i)
	 for(j=0;j<2;++j)
	  if(ch[i][j]) dd[ch[i][j]]++;
	que.push(1);dp[1]=1;
	for(;!que.empty();){
		a=que.front();que.pop();b=dp[a];
		ad(ans[tim[a]],b);
		for(i1=0;i1<2;++i1) if(ch[a][i1]){
			ad(dp[(j1=ch[a][i1])],b);
			for(i2=0;i2<2;++i2) if(ch[j1][i2]){
				ad(dp[(j2=ch[j1][i2])],b);
				for(i3=0;i3<2;++i3) if(ch[j2][i3]){
					ad(dp[(j3=ch[j2][i3])],b);
					for(i4=0;i4<2;++i4) if(ch[j3][i4]){
						ad(dp[ch[j3][i4]],b);
					}
				}
				
			}
		}
		if(ch[ch[ch[ch[a][0]][0]][1]][1])
			dc(dp[ch[ch[ch[ch[a][0]][0]][1]][1]],b);
		if(ch[ch[ch[ch[a][0]][1]][0]][1])
		    dc(dp[ch[ch[ch[ch[a][0]][1]][0]][1]],b);
		if(ch[ch[ch[ch[a][1]][1]][1]][0])
		    dc(dp[ch[ch[ch[ch[a][1]][1]][1]][0]],b);
		if(ch[ch[ch[ch[a][1]][1]][1]][1])
		    dc(dp[ch[ch[ch[ch[a][1]][1]][1]][1]],b);
		for(j=0;j<2;++j) if(ch[a][j]){
		   dd[ch[a][j]]--;if(!dd[ch[a][j]]) que.push(ch[a][j]);
		}
	}
	for(i=1;i<n;++i) ad(ans[i+1],ans[i]);
	for(i=1;i<=n;++i) printf("%d\n",ans[i]);
	return 0;
}

D.Isolation

r = 1 n r=1-n 扫过去,动态维护 t i t_i 表示区间 [ i , r ] [i,r] 中只出现一次的数的个数,每次处理一下 a r a_r 和前驱之间的值再 d p dp 一下即可。

复杂度也比较玄学,题解没看懂,似乎是 O ( n n ) O(n\sqrt n) 的。


E.Legendary Tree

构造题一向比较巧妙:

设根为1。

首先可以 O ( n ) O(n) 求出每个点的子树大小 s z [ i ] sz[i] (询问1到其他所有点的路径经过点 i i 的个数)。

将点按 s z sz 升序排序,逐个找到每个点的儿子结点。

设集合 S S ,存储所有未找到父节点的结点,扫到结点 i i 时,暴力二分找到最小的 k k ,满足 1 1 s 1 , s 2 , . . . , s k s_1,s_2,...,s_k 的路径经过点 i i 的个数非0, k k 就是 i i 的儿子结点。

询问次数在 n + n log n n+n\log n 级别。

猜你喜欢

转载自blog.csdn.net/corsica6/article/details/87915132