[题解]NOIP 2024 T1~T2

编辑字符串(edit)

初见感觉像贪心,但在不好写+不会证的情况下放弃了,然后想到DP又设不出状态。

实际上……就是贪心哦?


下文称\(s_1,s_2\)分别为\(a,b\)

不难发现一个不存在锁定位置的区间,其内元素是可以任意交换的。

所以我们可以按照锁定位置,将两个字符串划分成若干个区间(被锁定的位置可以看作单独的一个区间)。

贪心的思路,就是一个指针\(i\)从头开始遍历,不断取\(a_i,b_i\)所在的区间,如果两个区间都存在未被匹配的\(0\),就用\(0\)进行匹配,否则如果存在未被匹配的\(1\),就用\(1\)匹配。

正确性仔细一想其实比较显然。

我们各取\(a,b\)最左边的区间\(A\)\(B\)(显然它们是左端对齐的),假设\(|A|\ge |B|\)

按贪心对两个区间进行匹配后,如果将\(A\)长度为\(|B|\)的前缀中任选一个元素交换到此前缀之后:

  • 如果交换出去后,该前缀对答案的贡献\(-1\):因为交换出去的那个元素最多能产生\(+1\)的贡献,所以最好情况下也只能使答案不变。
  • 如果交换出去后,该前缀对答案的贡献不变:说明交换的两个值相等,所以答案不变。
  • 如果交换出去后,该前缀对答案的贡献\(+1\):根据贪心的过程,一定不存在这种情况。
点击查看代码
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int t,n,pa[N],pb[N],ca[N][2],cb[N][2],ans;
string a,b,c,d;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
	cin>>t;
	while(t--){
		memset(ca,0,sizeof ca);
		memset(cb,0,sizeof cb);
		cin>>n>>a>>b>>c>>d;
		for(int i=0,p=0;i<n;i++){
			if(c[i]&1) pa[i]=((~p)?p:(p=i));
			else pa[i]=i,p=-1;
		}
		for(int i=0,p=0;i<n;i++){
			if(d[i]&1) pb[i]=((~p)?p:(p=i));
			else pb[i]=i,p=-1;
		}
		for(int i=0;i<n;i++)
			ca[pa[i]][a[i]&1]++,cb[pb[i]][b[i]&1]++;
		ans=0;
		for(int i=0;i<n;i++){
			if(ca[pa[i]][0]&&cb[pb[i]][0])
				ans++,ca[pa[i]][0]--,cb[pb[i]][0]--;
			else if(ca[pa[i]][1]&&cb[pb[i]][1])
				ans++,ca[pa[i]][1]--,cb[pb[i]][1]--;
		}
		cout<<ans<<"\n";
	}
	return 0;
}

遗失的赋值(assign)

如果给定的一元关系本就存在矛盾,特判输出\(0\)即可。我们只分析一元关系不存在矛盾的情况。

一种二元关系的构造是不合法的,当且仅当存在二元关系区间\([l,r)\),满足:

  • \(x_l,x_r\)的值均是一元关系给定的;
  • \(a_l=x_l\)
  • 对于\(i\in(l,r)\),都有\(a_i=b_{i-1}\)
  • \(b_{r-1}\ne x_r\)

进一步我们发现,将第\(1\)条(条件A)替换成“\([l,r]\)仅有 \(x_l,x_r\)的值是一元关系给定的”(条件B),这样判定仍然是正确的。因为每一个满足条件A的不合法的大区间\([l,r]\),都包含同样满足条件B的不合法的小区间\([l',r']\)
比如下图,红色线段是二元限制,绿点是一元限制。\([12,16)\)这个满足条件A的不合法区间,就包含\([14,16)\)这个满足条件B的不合法区间。

所以我们可以将一元关系从小到大排序,依次考虑相邻的一元关系之间,合法的构造个数,用乘法原理计算答案即可。

对于\(x_l,x_r\)两个相邻的一元关系,它们之间合法的构造个数就是总个数\(-\)不合法的构造个数。

  • \(l,r\)之间有\(r-l\)个二元关系,每个二元关系有\(2\)个值,所以总个数是\(v^{2(r-l)}\)
  • 根据上面的判定条件,我们要让\([l,r)\)之间的二元关系连成一条链,其中左端点只能选\(x_l\),中间的点都可以随便选择,右端点可以选除了\(x_r\)以外的任何值。所以不合法的构造数是\(v^{r-l-1}\times (v-1)\)

相减,得到\(v^{2(r-l)}-v^{r-l-1}\times (v-1)\)。用所有\(l,r\)的答案累乘起来就可以了(所以答案其实是和\(d\)无关的)。

注意,还需要额外累乘最左边和最右边的答案,显然这两处都可以随便构造,所以直接累乘合法的数量即可。

每组测试数据\(O(m(\log m+\log n))\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
#define M 100010
using namespace std;
struct Limit{int c,d;}lim[M];
int t,n,m,v;
int qpow(int a,int n){
	int ans=1;
	while(n){
		if(n&1) ans=ans*a%mod;
		a=a*a%mod,n>>=1;
	}
	return ans;
}
int solve(){
	sort(lim+1,lim+1+m,[](Limit a,Limit b){return a.c<b.c;});
	for(int i=1;i<m;i++)
		if(lim[i].c==lim[i+1].c&&lim[i].d!=lim[i+1].d) return 0;
	int ans=1;
	for(int i=1;i<m;i++){
		int l=lim[i].c,r=lim[i+1].c;
		if(l==r) continue;
		ans=ans*((qpow(v,(r-l)<<1)-qpow(v,r-l-1)*(v-1))%mod+mod)%mod;
	}
	ans=ans*qpow(v,(lim[1].c-1)<<1)%mod;
	ans=ans*qpow(v,(n-lim[m].c)<<1)%mod;
	return ans;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>t;
	while(t--){
		cin>>n>>m>>v;
		for(int i=1;i<=m;i++) cin>>lim[i].c>>lim[i].d;
		cout<<solve()<<"\n";
	}
	return 0;
}

树的遍历(traverse)

我们将边看作点,构建一个新图。新图上的两点之间有边,当且仅当它们对应原图上的两边有公共顶点。这样构建出的图称为原图的线图,这道题就是指定了线图上的若干个关键点,让我们求出从这些关键点开始遍历能得到的DFS树的形态数。

\(k=1\)的情况比较送,因为不难发现原图上某点的邻接边,在线图上构成了一个团(完全子图)。一个完全图的DFS树是一条链,由于原图是一棵树,所以指定DFS树的根节点后,每个团第一个被遍历到的点是确定的,而其他点顺序任意。所以答案是\(\prod\limits_{u\in {V}} (deg_u-1)!\),其中\(V\)是原图上的点集,\(deg_u\)\(u\)的度数。

正解之后补。

posted @ 2024-12-03 17:19  Sinktank  阅读(29)  评论(0编辑  收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.