• 20联赛集训day4 题解


    A

    考虑对串的反串建后缀树,答案就是叶子节点个数。

    注意到这时的叶子节点一定是一段前缀,如果一个前缀 (s[1ldots i]) 是叶子节点当且仅当它不属于任何一个点的 border 集合,所以问题变成了求每个前缀的 border 集合的并的大小,用 kmp 解决即可。

    这里发现一个结论:每个前缀的 border 集合的并的大小等于 (max{fail_i})

    证明要考虑这个命题等价于叶子节点在值域上是连续的一段后缀,然后考虑反证:假设前缀 (x) 不属于但是 (y) 属于,一定能从 (y) 属于的那个前缀找出一部分变成 (x) 属于的。(然而我考试直接找规律)

    所以有个结论:后缀树的叶子节点个数,每个前缀的 border 集合的并的大小,本质不同的 (fail_i) 的个数,( ext{max} fail_i) 的值是相等的。

    不管怎么说,即使在 NOIP 赛场上遇到字符串题也要想想能否用后缀树的思想方便优化。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 1e6 + 5;
    
    char str[MAXN];
    int n;
    int nxt[MAXN];
    
    int main(){
    	scanf("%s",str+1);n = strlen(str+1);
    	int j = 0;nxt[0] = -1;nxt[1] = 0;int ans = 0;
    	FOR(i,2,n){
    		while(j && str[i] != str[j+1]) j = nxt[j];
    		if(str[i] == str[j+1]) ++j;
    		else j = 0;
    		nxt[i] = j;
    		ans = std::max(ans,nxt[i]);
    	}
    	printf("%d
    ",n-ans);
    	return 0;
    }
    

    B

    首先一个很松的上界的是 (ans leq n),我们设所有三元组三个数的和的和为 (S),三元组个数为 (k),可以发现 (kn = S geq 3(1+2+ldots+k)),可以得到 (k leq lfloor frac{2n}{3} floor -1)

    写个剪枝搜索应该是能跑出来 (n leq 17) 的,于是打表可以发现规律。

    我们将 (n) 按照 (n mod 3) 分类,我们发现 (n mod 3=0) 比较特殊,找找这种方案的规律:

    首先我们只需要考虑二元组 ((a_i,b_i)) 满足和互不相同,(a_i) 互不相同,(b_i) 互不相同即可。

    我们 (a_i,b_i) 的值域都是 ([1,ans]),我们考虑 (a_i) 前面全放奇数后面全放偶数,然后把 (b_i) 最小的若干个倒着和 (a_i) 的奇数部分匹配,剩下的倒着和 (a_i) 的偶数部分匹配。举个 (n=12) 的例子:

    1 4
    3 3
    5 2
    7 1
    2 7
    4 6
    6 5
    

    首先 (a_i,b_i) 一定互不相同。然后发现这样排列上一个和下一个的和一定会相差 (1),所以都满足条件。

    (n mod 3 = 1) 的答案是不变的,改一下 (c_i) 就行了。

    (n mod 3 = 2) 的答案变了,我们让 (a_i) 整体加一,(c_i) 整体加一,发现 (a,c) 都可以放 (1) 了,然后 (b)(n-2) 即可。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 5e5 + 5;
    int a[MAXN],b[MAXN],c[MAXN],n,ans;
    bool vis[3][MAXN];
    
    inline void check(){
    	assert(ans == (2*n-3)/3);
    	// DEBUG(ans);
    	FOR(i,1,ans){
    		// DEBUG(i);
    		assert(a[i]+b[i]+c[i] == n);
    		assert(!vis[0][a[i]]);
    		assert(!vis[1][b[i]]);
    		assert(!vis[2][c[i]]);
    		vis[0][a[i]] = vis[1][b[i]] = vis[2][c[i]] = 1;
    	}
    }
    
    int main(){
    	scanf("%d",&n);
    	if(n == 1){
    		puts("0");
    		return 0;
    	}
    	if(n == 2){
    		puts("0");
    		return 0;
    	}
    	if(n%3 == 0){
    		ans = 2*(n/3)-1;
    		printf("%d
    ",ans);
    		int t = 0;
    		for(int i = 1;i <= ans;i += 2) a[++t] = i;
    		int t1 = t;
    		for(int i = 2;i <= ans;i += 2) a[++t] = i;
    		t = 0;
    		ROF(i,t1,1) b[i] = ++t;
    		ROF(i,ans,t1+1) b[i] = ++t;
    		FOR(i,1,ans) c[i] = n-a[i]-b[i];
    		FOR(i,1,ans) printf("%d %d %d
    ",a[i],b[i],c[i]);
    		check();
    		return 0;
    	}
    	if(n%3 == 1){
    		--n;
    		ans = 2*(n/3)-1;
    		printf("%d
    ",ans);
    		int t = 0;
    		for(int i = 1;i <= ans;i += 2) a[++t] = i;
    		int t1 = t;
    		for(int i = 2;i <= ans;i += 2) a[++t] = i;
    		t = 0;
    		ROF(i,t1,1) b[i] = ++t;
    		ROF(i,ans,t1+1) b[i] = ++t;
    		FOR(i,1,ans) c[i] = n-a[i]-b[i]+1;
    		FOR(i,1,ans) printf("%d %d %d
    ",a[i],b[i],c[i]);
    		++n;
    		check();
    		return 0;
    	}
    	if(n%3 == 2){
    		n -= 2;
    		ans = 2*(n/3)-1;
    		int t = 0;
    		for(int i = 1;i <= ans;i += 2) a[++t] = i;
    		int t1 = t;
    		for(int i = 2;i <= ans;i += 2) a[++t] = i;
    		t = 0;
    		ROF(i,t1,1) b[i] = ++t;
    		ROF(i,ans,t1+1) b[i] = ++t;
    		FOR(i,1,ans) c[i] = n-a[i]-b[i]+1;
    		FOR(i,1,ans) a[i]++;
    		// 第一列有1 第三列有1 
    		++ans;
    		n += 2;
    		a[ans] = 1;c[ans] = 1;b[ans] = n-2;
    		printf("%d
    ",ans);
    		FOR(i,1,ans) printf("%d %d %d
    ",a[i],b[i],c[i]);
    		check();
    		return 0;
    	}
    	return 0;
    }
    

    C

    首先注意数字互不相同,所以能分成若干条链,限制形如某条链上相邻的两个点原序列不能相邻(注意我这里思考的链的元素是相邻关系,也就是若干个 pair ,所以选了链上一个点对应了固定了原序列两个点,选了相邻两个对应固定三个,不相连两个对应固定四个)

    考虑容斥,枚举有几个相邻的位置 (i),有一对相邻的位置就可以将两个点并起来,所以排列的方案数就是 ((n-i)!)

    现在只需要求出来有 (i) 个相邻的位置的方案数就好了。

    链与链之间的关系是独立的,我们考虑如果只有一条链怎么做(就是样例 (1) 的情况)。

    我们钦定链上一些连通块选了,那么每个连通块可以正着也可以反着,对答案有 (2) 的贡献。于是搞个 dp:(f_{i,j,0/1}) 考虑前 (i) 个 pair ,选了 (j) 个 pair ,上一个是否选。转移如果上一个是 (0) 并且这一个是 (1) 方案数就要 ( imes 2)

    不同链之间是独立的;直接背包合并是爆炸的,不如把所有链拼在一起,在分隔点打标记,每次强制断开即可。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 5000+5;
    const int ha = 998244353;
    int n,k,a[MAXN];
    
    namespace Subtask1{
    	inline bool check(){
    		return n <= 10;
    	}
    	
    	int p[MAXN];
    	
    	inline void Solve(){
    		FOR(i,1,n) p[i] = i;
    		int ans = 0;
    		do{
    			bool flag = 1;
    			FOR(i,2,n) if(std::abs(a[p[i-1]]-a[p[i]]) == k) {flag = 0;break;}
    			ans += flag;
    		}while(std::next_permutation(p+1,p+n+1));
    		printf("%d
    ",ans);exit(0);
    	}
    }
    
    inline void add(int &x,int y){
    	x += y;if(x >= ha) x -= ha;
    }
    
    namespace Subtask2{
    	int f[MAXN][MAXN][2],fac[MAXN];
    	inline bool check(){
    		return n <= 5000;
    	}
    	
    	bool qd[MAXN];
    	int g[MAXN];
    	std::map<int,int> S;
    	int to[MAXN],len[MAXN],tot;
    	bool vis[MAXN];
    	
    	inline void Solve(){
    		FOR(i,1,n) S[a[i]] = i;
    		FOR(i,1,n){
    			S[a[i]] = 0;
    			to[i] = S[a[i]+k];
    		}
    		int sz = 0;
    		FOR(i,1,n){
    			if(!vis[i]){
    				int v = i;++tot;
     				while(v){
     					++len[tot];vis[v] = 1;
     					v = to[v];
     				}
     				len[tot]--;
     				if(!len[tot]) --tot;
     				else sz += len[tot];
    			}
    		}
    		int sm = 0;
    		FOR(i,1,tot){
    			sm += len[i];
    			qd[sm+1] = 1;
    		}
    		f[0][0][0] = 1;
    		fac[0] = 1;FOR(i,1,MAXN-1) fac[i] = 1ll*fac[i-1]*i%ha;
    		FOR(i,1,sz){
    			FOR(j,0,i){
    				f[i][j][0] = (f[i-1][j][0]+f[i-1][j][1])%ha;
    				if(qd[i]){
    					if(j) f[i][j][1] = 2ll*(f[i-1][j-1][0]+f[i-1][j-1][1])%ha;
    				}
    				else{
    					if(j) add(f[i][j][1],f[i-1][j-1][1]);
    					if(j) add(f[i][j][1],2ll*f[i-1][j-1][0]%ha);
    				}
    			}
    		}
    		FOR(i,0,sz) g[i] = (f[sz][i][0]+f[sz][i][1])%ha;
    		int ans = 0;
    		FOR(i,0,sz){// 选了几个球
    			int gx = 1ll*fac[n-i]*g[i]%ha;
    			if(i&1) gx = ha-gx;
    			add(ans,gx);
    		}
    		printf("%d
    ",ans);
    	}
    }
    
    int main(){
    	scanf("%d%d",&n,&k);
    	FOR(i,1,n) scanf("%d",a+i);
    	if(Subtask1::check()) Subtask1::Solve();
    	if(Subtask2::check()) Subtask2::Solve();
    	return 0;
    }
    /*
    注意 这个题没有相同的数
    所以限制只有 O(n) 对
    
    n个球 染黑m个 连续段
    */
    
  • 相关阅读:
    附加数据库对于服务器失败5120
    c#程序调试没有问题,Ctrl+F5运行就挂
    c#让某个窗体一直显示于桌面最上方
    c#调用GrADS
    备份对于服务器“”失败,加载的介质已格式化为支持1个介质簇,但根据指定的备份设备,应支持2个介质簇
    Assembly.Load()出错
    对控件部分的截图和录像
    创建oracle本地数据库步骤详解
    基于windows的resin配置
    oracle 插入记录,字段自动获取当前系统时间(YYYY-MM-DD HH24:MI:SS)
  • 原文地址:https://www.cnblogs.com/rainair/p/14305506.html
Copyright © 2020-2023  润新知