题解 CF1327 D,F,G Infinite Path, AND Segments, Letters and Question Marks

比赛链接

CF1327D Infinite Path

首先,题目关于Infinite Path的定义,其实就是要找到排列中的一个循环圈,使得圈里的每个位置颜色相同。

对每个位置\(i\),我们建一条\(i\rightarrow p_i\)的有向边。则每个循环圈就是图上的一个环。

考虑一次乘法操作对排列的影响。注意:\(a\times b\)中的\(b\)是原排列,\(a\)是之前乘法的结果。也就是说,一次操作相当于是把之前乘法的结果,作用于原排列上。

考虑第一次操作后,也即\(p^2\)。此时每个元素的边会指向它后继的后继。也即:第\(i\)个点指向了\(i\)之后的第\(2\)个点。发现,如果环的长度为偶数,此时原来的环会分裂为\(2\)个环。

再考虑第二次操作后,因为是把当前的排列(\(p^2\))作用在原排列上,所以每个元素会指向它之后的第\(3\)个节点。发现,如果环的长度是\(3\)的倍数,此时原来的环会分裂为\(3\)个环。

由此可以发现:对于\(p^k\),它的效果实际就是让每个\(i\)指向\(i\)之后的第\(k\)个元素。如果环长是\(k\)的倍数,此时原来的环会分裂为\(k\)个环。

因为目标是要让某个环上颜色全部相同。显然如果当前环不满足条件,则只有使它分裂才有可能满足条件。因此,合法的\(k\)一定是环长的因数。

我们枚举环长度的所有因数。暴力check分裂出的所有小环中是否存在一个环颜色全部相同。

最坏情况下,当\(k\)等于环长时,每个点都指向了它自己(即“自环”),此时每个环的颜色一定相同(因为环上只有一个点)。所以一定有解。

时间复杂度\(O(n\sqrt{n})\)

参考代码:

//problem:
#include <bits/stdc++.h>
using namespace std;
 
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
 
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
 
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=2e5;
int n,p[MAXN+5],c[MAXN+5],vis[MAXN+5],cnt,f[MAXN+5];
vector<int>vec[MAXN+5];
void dfs(int i){
	if(vis[i])return;
	vis[i]=1;vec[cnt].pb(i);
	dfs(p[i]);
}
int main() {
	int T=read();while(T--){
		n=read();
		for(int i=1;i<=n;++i)p[i]=read(),vis[i]=0,vec[i].clear();
		for(int i=1;i<=n;++i)c[i]=read();cnt=0;
		for(int i=1;i<=n;++i){
			if(!vis[i])cnt++,dfs(i);
		}
		int ans=MAXN;
		for(int i=1;i<=cnt;++i){
			//cout<<"* ";for(uint j=0;j<vec[i].size();++j)cout<<vec[i][j]<<" ";cout<<endl;
			int x=vec[i].size();
			for(int k=1;k<=x;++k)if(x%k==0){
				for(int j=0;j<k;++j)f[j]=0;
				for(int j=k;j<(int)vec[i].size();++j)if(c[vec[i][j]]!=c[vec[i][j-k]])f[j%k]=1;
				bool ok=0;
				for(int j=0;j<k;++j)if(!f[j]){ok=1;break;}
				if(ok){ans=min(ans,k);break;}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

CF1327F AND Segments

因为位运算每一位是独立的,我们可以按位考虑,再把每一位的方案数乘起来。

考虑某一位\(k\)。对于限制\((l,r,x)\),分两类情况:

  1. 如果\(x\)的第\(k\)位为\(1\),相当于要求区间\([l,r]\)的值全部为\(1\)
  2. 如果\(x\)的第\(k\)位为\(0\),相当于要求区间\([l,r]\)中至少有一个\(0\)

容易想到一个\(O(n^3)\)的DP:设\(dp[i][j]\)表示考虑了前\(i\)个位置,上一个\(0\)\(j\)时的方案数。

对于第一类限制比较好处理,只要第\(i\)位被确定为\(1\),就不做\(dp[i][i]\)的转移即可。对于第二类限制,可以对每个\(r\),记录它所在的所有限制里最大的\(l\),记为\(maxl\)。则转移时不考虑\(dp[i-1][0]\sim dp[i-1][maxl-1]\)即可。

考虑优化。可以用线段树来维护DP数组的第二维。发现需要支持三个操作:

  1. 单点修改(即更新出\(dp[i][i]\)的值)
  2. 求全局和
  3. 把一个前缀置为\(0\)

时间复杂度\(O(kn\log n)\)

参考代码:

//problem:CF1327F
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=5e5+5,MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
int n,K,m,b[MAXN+5],c[MAXN+5];
struct Limits{int l,r,x;}a[MAXN+5];
struct SegmentTree{
	int sum[MAXN*4+5],tag[MAXN*4+5];
	void push_up(int p){
		sum[p]=mod1(sum[p<<1]+sum[p<<1|1]);
	}
	void build(int p,int l,int r){
		sum[p]=tag[p]=0;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		push_up(p);
	}
	void push_down(int p){
		if(tag[p]){
			sum[p<<1]=0;
			tag[p<<1]=1;
			sum[p<<1|1]=0;
			tag[p<<1|1]=1;
			tag[p]=0;
		}
	}
	void set0(int p,int l,int r,int ql,int qr){
		if(ql<=l&&qr>=r){
			sum[p]=0;
			tag[p]=1;
			return;
		}
		push_down(p);
		int mid=(l+r)>>1;
		if(ql<=mid)set0(p<<1,l,mid,ql,qr);
		if(qr>mid)set0(p<<1|1,mid+1,r,ql,qr);
		push_up(p);
	}
	void modify(int p,int l,int r,int pos,int v){
		if(l==r){
			sum[p]=v;
			return;
		}
		push_down(p);
		int mid=(l+r)>>1;
		if(pos<=mid)modify(p<<1,l,mid,pos,v);
		else modify(p<<1|1,mid+1,r,pos,v);
		push_up(p);
	}
}T;
int calc(int k){
	for(int i=1;i<=n+1;++i)b[i]=c[i]=0;
	for(int i=1;i<=m;++i){
		if((a[i].x>>k)&1){
			c[a[i].l]++;
			c[a[i].r+1]--;
		}
		else{
			b[a[i].r+1]=max(b[a[i].r+1],a[i].l);
		}
	}
	for(int i=1;i<=n;++i)c[i]+=c[i-1];
	T.build(1,0,n+1);
	T.modify(1,0,n+1,0,1);
	for(int i=1;i<=n+1;++i){
		if(b[i]){
			T.set0(1,0,n+1,0,b[i]-1);
		}
		if(i==n+1)break;
		if(!c[i])T.modify(1,0,n+1,i,T.sum[1]);
	}
	return T.sum[1];
}
int main() {
	n=read();K=read();m=read();
	for(int i=1;i<=m;++i)a[i].l=read(),a[i].r=read(),a[i].x=read();
	int ans=1;
	for(int i=0;i<K;++i){
		ans=(ll)ans*calc(i)%MOD;
		if(!ans)break;
	}
	printf("%d\n",ans);
	return 0;
}

CF1327G Letters and Question Marks

考虑如果已经确定了这些问号的位置放什么字母,如何计算\(val(S)=\sum_i F(S,t_i)\cdot c_i\)。可以对所有\(t\)串建一个AC自动机。插入一个串的时候让结束位置的那个节点权值加上\(c_i\)。建fail树的时候让每个节点的权值等于它fail树上所有祖先原来的权值之和。这样,拿串\(S\)在AC自动机上跑一遍,所有经过的节点的权值和,就是\(val(S)\)了。

现在让我们自己来决策这些问号的位置该放什么字符。容易想到使用动态规划算法。

朴素的想法是,设\(dp[i][mask][u]\)表示考虑了串\(S\)的前\(i\)位,已经使用了\(mask\)里的这些字符,匹配到了AC自动机上的节点\(u\)时,能得到的最大权值。时间复杂度\(O(|S|L2^K)\),其中\(L\)表示所有\(t\)串的长度之和,\(K\)表示字符集大小。

考虑优化上述算法。发现在\(S\)里已经确定的位置上转移是十分浪费时间的。\(S\)串,被?划分为了\(O(K)\)段,每一段中的字符都是已知的。为了优化DP过程,我们预处理一个数组\(tr[u][i]\)表示从AC自动机上的节点\(u\)出发,匹配了\(S\)串的第\(i\)个确定段后,会到达哪个节点。预处理出\(tr\)数组的时间复杂度为\(O(|S|L)\)

然后我们就可以把DP状态简化为:\(dp[mask][u]\)表示考虑了\(S\)中前\(\operatorname{bitcnt}(mask)\)个确定段,现在走到了AC自动机上节点\(u\)时,能获得的最大权值。DP的复杂度变为\(O(2^KLK)\)

总时间复杂度\(O(|S|L+2^KLK)\)。可以通过本题。

参考代码:

//problem:CF1327G
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=4e5,MAXT=1000,MAXK=14;
const ll INF=1e18;
int m,val[MAXT+5],n,cnt,tr[MAXT+5][MAXK+5];
ll dp[1<<MAXK][MAXT+5],trval[MAXT+5][MAXK+5];
pii seg[MAXK+5];
char tmp[MAXT+5],s[MAXN+5];
struct ACauto{
	int mp[MAXT+5][26],fa[MAXT+5],cnt;
	ll acval[MAXT+5];
	void ins(char *s,int n,int v){
		int u=1;
		for(int i=1;i<=n;++i){
			if(!mp[u][s[i]-'a'])mp[u][s[i]-'a']=++cnt;
			u=mp[u][s[i]-'a'];
		}
		acval[u]+=v;
	}
	void build(){
		for(int i=0;i<26;++i)mp[0][i]=1;
		queue<int>q;q.push(1);fa[1]=0;
		while(!q.empty()){
			int u=q.front();q.pop();
			acval[u]+=acval[fa[u]];//!!!!!
			for(int i=0;i<26;++i){
				if(mp[u][i]){
					fa[mp[u][i]]=mp[fa[u]][i];
					q.push(mp[u][i]);
				}
				else mp[u][i]=mp[fa[u]][i];
			}
		}
	}
	ACauto(){cnt=1;}
}ac;

int main() {
	scanf("%d",&m);
	for(int i=1;i<=m;++i){
		scanf("%s%d",tmp+1,&val[i]);
		int len=strlen(tmp+1);
		ac.ins(tmp,len,val[i]);
	}
	ac.build();
	scanf("%s",s+1);
	n=strlen(s+1);
	int lst=0;
	for(int i=1;i<=n+1;++i){
		if(i==n+1||s[i]=='?'){
			seg[++cnt]=mk(lst+1,i-1);
			lst=i;
		}
	}
	for(int i=1;i<=ac.cnt;++i){
		for(int j=1;j<=cnt;++j){
			int u=i;
			for(int k=seg[j].fi;k<=seg[j].se;++k){
				u=ac.mp[u][s[k]-'a'];
				trval[i][j]+=ac.acval[u];
			}
			tr[i][j]=u;
		}
	}
	memset(dp,0xcf,sizeof(dp));
	dp[0][tr[1][1]]=trval[1][1];
	for(int i=1;i<(1<<MAXK);++i){
		int t=__builtin_popcount(i);
		if(t>=cnt)continue;
		for(int j=0;j<MAXK;++j)if((i>>j)&1){
			for(int k=1;k<=ac.cnt;++k){
				int x=ac.mp[k][j];
				int y=tr[x][t+1];
				dp[i][y]=max(dp[i][y],dp[i^(1<<j)][k]+trval[x][t+1]+ac.acval[x]);
			}
		}
	}
	ll ans=-INF;
	for(int i=0;i<(1<<MAXK);++i){
		if(__builtin_popcount(i)!=cnt-1)continue;
		for(int j=1;j<=ac.cnt;++j)ans=max(ans,dp[i][j]);
	}
	cout<<ans<<endl;
	return 0;
}
posted @ 2020-03-31 15:18  duyiblue  阅读(203)  评论(2编辑  收藏  举报