题解 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)\),分两类情况:
- 如果\(x\)的第\(k\)位为\(1\),相当于要求区间\([l,r]\)的值全部为\(1\);
- 如果\(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数组的第二维。发现需要支持三个操作:
- 单点修改(即更新出\(dp[i][i]\)的值)
- 求全局和
- 把一个前缀置为\(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;
}