P1006 传纸条
解:题意可以转化为两个人从起点出发到终点的不重复路径,定义数组dp[i][j][k],表示前i步第一个人在j列,第二个人在k列的最大满意度。
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
int a[100][100];
int dp[200][100][100];
int n,m;
int main()
{
scanf("%d %d",&m,&n);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
}
}
memset(dp,-1,sizeof dp);
dp[2][1][1]=0;
for(int i=3;i<n+m;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=j+1;k<=n;k++)
{
int tmp=dp[i][j][k];
if(dp[i-1][j][k]>tmp) tmp=dp[i-1][j][k];
if(dp[i-1][j-1][k]>tmp) tmp=dp[i-1][j-1][k];
if(dp[i-1][j][k-1]>tmp) tmp=dp[i-1][j][k-1];
if(dp[i-1][j-1][k-1]>tmp) tmp=dp[i-1][j-1][k-1];
if(tmp==-1) continue;
dp[i][j][k]=tmp+a[i-j][j]+a[i-k][k];
}
}
}
printf("%d\n",max(dp[n+m-1][n][n-1],dp[n+m-1][n-1][n]));
return 0;
}
P1417 烹调方案
解:乍一看我觉得事背包问题,其实b[i]这个属性对于顺序是有影响的。
两种情况:当i先被选时有
当j先被选时有。
当i应当被选时有,化简后即为
按照这个结果排个序。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct node
{
ll a,b,c;
}p[100];
int T,n;
ll dp[100010];
bool cmp(node x,node y)
{
return -y.b*x.c>-x.b*y.c;
}
int main()
{
scanf("%d %d",&T,&n);
for(int i=1;i<=n;i++) scanf("%d",&p[i].a);
for(int i=1;i<=n;i++) scanf("%d",&p[i].b);
for(int i=1;i<=n;i++) scanf("%d",&p[i].c);
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n;i++)
{
for(int j=T;j>=p[i].c;j--)
{
dp[j]=max(dp[j-p[i].c]+p[i].a-j*p[i].b,dp[j]);
}
}
ll ans=0;
for(int i=1;i<=T;i++) ans=max(ans,dp[i]);
printf("%d\n",ans);
return 0;
}
P1736 创意吃鱼法
解:用两个数组a,b分别维护向左(右)和向上的最长的0(不包括当前点)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int dp[2510][2510];
int n,m;
int a[2510][2510];
int L[2510][2510],up[2510][2510];
int main()
{
while(scanf("%d %d",&n,&m)!=EOF)
{
memset(L,0,sizeof L);
memset(up,0,sizeof up);
memset(dp,0,sizeof dp);
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(!a[i][j])
{
L[i][j]=L[i][j-1]+1;
up[i][j]=up[i-1][j]+1;
}
else
{
dp[i][j]=min(dp[i-1][j-1],min(L[i][j-1],up[i-1][j]))+1;
ans=max(ans,dp[i][j]);
}
}
}
memset(L,0,sizeof L);
memset(up,0,sizeof up);
memset(dp,0,sizeof dp);
for(int i=1;i<=n;i++)
{
for(int j=m;j>=1;j--)
{
if(!a[i][j])
{
L[i][j]=L[i][j+1]+1;
up[i][j]=up[i-1][j]+1;
}
else
{
dp[i][j]=min(dp[i-1][j+1],min(L[i][j+1],up[i-1][j]))+1;
ans=max(ans,dp[i][j]);
}
}
}
printf("%d\n",ans);
}
return 0;
}
P1064 金明的预算方案
解:有依赖的背包问题。因为本题的主件最多只有2个附件。那么这个主机的所有附件组合集合可以直接枚举。转化为分组背包问题,每组的物品即是该主件和附件的所有组合情况。
小数据写法
#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct node
{
int v,p,q;
};
node a[100];
vector<node> g[100];
vector<node> p[100];
int dp[32010];
int N,m;
int main()
{
scanf("%d %d",&N,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&a[i].v,&a[i].p,&a[i].q);
if(a[i].q)
{
g[a[i].q].push_back(a[i]);
}
else g[i].push_back(a[i]);
}
for(int i=1;i<=m;i++)
{
if(g[i].size()==3)
{
p[i].push_back(node{g[i][0].v,g[i][0].v*g[i][0].p,0});
p[i].push_back(node{g[i][0].v+g[i][1].v,g[i][0].v*g[i][0].p+g[i][1].v*g[i][1].p,0});
p[i].push_back(node{g[i][0].v+g[i][2].v,g[i][0].v*g[i][0].p+g[i][2].v*g[i][2].p,0});
p[i].push_back(node{g[i][0].v+g[i][2].v+g[i][1].v,g[i][0].v*g[i][0].p+g[i][1].v*g[i][1].p+g[i][2].v*g[i][2].p,0});
}
else if(g[i].size()==2)
{
p[i].push_back(node{g[i][0].v,g[i][0].v*g[i][0].p,0});
p[i].push_back(node{g[i][0].v+g[i][1].v,g[i][0].v*g[i][0].p+g[i][1].v*g[i][1].p,0});
}
else if(g[i].size()==1)
{
p[i].push_back(node{g[i][0].v,g[i][0].v*g[i][0].p,0});
}
}
for(int i=1;i<=m;i++)
{
if(p[i].size()!=0)
{
for(int j=N;j>=0;j--)
{
for(int k=0;k<p[i].size();k++)
{
if(j>=p[i][k].v)
dp[j]=max(dp[j],dp[j-p[i][k].v]+p[i][k].p);
}
}
}
}
printf("%d\n",dp[N]);
return 0;
}
大数据写法(貌似我的常熟不够优秀)(做法参见背包九讲2.0)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct node
{
int v,p,q;
};
node a[1010];
vector<node> g[1010];
vector<node> p[1010];
int dp[100010];
int N,m;
int main()
{
scanf("%d %d",&N,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&a[i].v,&a[i].p,&a[i].q);
if(a[i].q)
{
g[a[i].q].push_back(a[i]);
}
else g[i].push_back(a[i]);
}
for(int i=1;i<=m;i++)
{
if(g[i].size()!=0)
{
memset(dp,-1,sizeof dp);
dp[0]=0;
for(int j=1;j<(int)g[i].size();j++){
for(int k=N-g[i][0].v;k>=g[i][j].v;k--)
{
if(dp[k]<dp[k-g[i][j].v]+g[i][j].v*g[i][j].p&&dp[k-g[i][j].v]!=-1)
dp[k]=dp[k-g[i][j].v]+g[i][j].v*g[i][j].p;
}
}
for(int j=0;j<=N-g[i][0].v;j++)
{
if(dp[j]!=-1)
{
p[i].push_back(node{j+g[i][0].v,dp[j]+g[i][0].v*g[i][0].p,0});
}
}
}
}
memset(dp,0,sizeof dp);
for(int i=1;i<=m;i++)
{
if(p[i].size()!=0)
{
for(int j=N;j>=0;j--)
{
for(int k=0;k<p[i].size();k++)
{
if(j>=p[i][k].v)
dp[j]=max(dp[j],dp[j-p[i][k].v]+p[i][k].p);
}
}
}
}
printf("%d\n",dp[N]);
return 0;
}
P1541 乌龟棋
定义dp[i][j][k][h],表示当选i张为1的牌、j张为2的牌、k张为3的牌和h张为4的牌的最大分数。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int N,M;
int a[400];
int b[10];
int dp[42][42][42][42];
int main()
{
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++) scanf("%d",&a[i]);
for(int j=1;j<=M;j++)
{
int x;
scanf("%d",&x);
b[x]++;
}
dp[0][0][0][0]=a[1];
for(int i=0;i<=b[1];i++)
{
for(int j=0;j<=b[2];j++)
{
for(int k=0;k<=b[3];k++)
{
for(int l=0;l<=b[4];l++)
{
int now=1+i+2*j+3*k+4*l;
if(i!=0)
dp[i][j][k][l]=max(dp[i][j][k][l],dp[i-1][j][k][l]+a[now]);
if(j!=0)
dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j-1][k][l]+a[now]);
if(k!=0)
dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j][k-1][l]+a[now]);
if(l!=0)
dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j][k][l-1]+a[now]);
}
}
}
}
printf("%d\n",dp[b[1]][b[2]][b[3]][b[4]]);
return 0;
}
问题记录:为何不能定义dp[i][j]表示用i张牌走到下标为j的时候的最大分数?
P1063 能量项链
解:发现转移方程形如大概就是区间dp了吧,本题定义dp[i][j]表示数串以a[i]开头,a[j]结尾的最大能量。因为本题是环形,只需要将数组a在复制一段,破环为链。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int N;
ll a[400];
ll dp[410][410];
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
{
scanf("%lld",&a[i]);
a[i+N]=a[i];
}
for(int i=2;i<=N;i++)
{
for(int j=1;j+i<=2*N;j++)
{
int k=j+i;
for(int l=j+1;l<=k-1;l++)
{
dp[j][k]=max(dp[j][k],dp[j][l]+dp[l][k]+a[j]*a[k]*a[l]);
}
}
}
ll ans=0;
for(int i=1;i<=N;i++) ans=max(ans,dp[i][i+N]);
printf("%lld\n",ans);
return 0;
}
P1052 过河
解:耳目一新,缩点。因为,即无论跳多远和怎么跳都会到达2520这个点,即石头之间的距离对2520取模即可将距离等效缩小。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int L;
int s,t,M;
int dp[300000];
ll a[200];
int stone[300000];
int flag[200];
int d[200];
int main()
{
scanf("%d",&L);
scanf("%d %d %d",&s,&t,&M);
for(int i=1;i<=M;i++) scanf("%lld",&a[i]);
sort(a+1,a+M+1);
for(int i=1;i<=M;i++)
{
flag[i]=(a[i]-a[i-1])%2520;
}
for(int i=1;i<=M;i++)
{
d[i]=flag[i]+d[i-1];
stone[d[i]]++;
}
L=d[M]+t;
for(int i=1;i<=L;i++) dp[i]=M;
dp[0]=0;
for(int i=1;i<=L;i++)
{
for(int j=s;j<=t;j++)
{
if(j>i) continue;
if(stone[i]) dp[i]=min(dp[i],dp[i-j]+stone[i]);
else dp[i]=min(dp[i],dp[i-j]);
}
}
int ans=M;
for(int i=d[M];i<d[M]+t;i++) ans=min(ans,dp[i]);
printf("%d\n",ans);
return 0;
}
Blue Jeans
解:求多个串的最长公共子串。枚举第一个字符串的所有子串用KMP与其他的串进行匹配。
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
string s[20];
int nexts[100];
vector<string> ans;
void getnext(string x)
{
int m=x.size();
int i,j;
j=nexts[0]=-1;
i=0;
while(i<m)
{
while(j!=-1&&x[i]!=x[j]) j=nexts[j];
nexts[++i]=++j;
}
}
bool kmp(string x,string y)
{
int n=y.size();
int m=x.size();
getnext(x);
int i,j;i=j=0;
while(i<n)
{
while(j!=-1&&x[j]!=y[i]) j=nexts[j];
++j;++i;
if(j>=m)
{
return true;
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--)
{
int M;
cin>>M;
for(int i=1;i<=M;i++)
{
cin>>s[i];
}
string com;
ans.clear();
for(int i=0;i<s[1].size();i++)
{
for(int j=i;j<s[1].size();j++)
{
com.clear();
for(int k=i;k<=j;k++) com+=s[1][k];
if((int)com.size()<3) continue;
int flag=0;
for(int k=2;k<=M;k++)
{
if(kmp(com,s[k])==true)
{
flag++;
}
}
if(flag==M-1) ans.push_back(com);
}
}
int mx=0;
for(int i=0;i<(int)ans.size();i++) mx=max(mx,(int)ans[i].size());
if(mx<3) cout<<"no significant commonalities"<<'\n';
else
{
sort(ans.begin(),ans.end());
for(int i=0;i<(int)ans.size();i++)
{
if((int)ans[i].size()==mx)
{
cout<<ans[i]<<'\n';
break;
}
}
}
}
return 0;
}
Count the string
解:KMP next数组的理解题。
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
#define mod 10007
char s[200010];
int nexts[200010];
void getnext(char x[])
{
int m=strlen(x);
int i,j;
j=nexts[0]=-1;
i=0;
while(i<m)
{
while(-1!=j&&x[i]!=x[j]) j=nexts[j];
nexts[++i]=++j;
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
scanf("%s",s);
getnext(s);
int ans=0;
for(int i=n;i>=0;i--)
{
int k=nexts[i];
while(k>0)
{
ans=(ans+1)%mod;
k=nexts[k];
}
}
printf("%d\n",(ans+n)%mod);
}
return 0;
}
String Problem
解:KMP求最小循环节和字符串的最大(小)表示法。
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
#define mod 10007
char s[1000010];
//char s1[300];
int nexts[1000010];
//char sub[300];
//char res[300];
void getnext(char x[])
{
int m=strlen(x);
int i,j;
j=nexts[0]=-1;
i=0;
while(i<m)
{
while(-1!=j&&x[i]!=x[j]) j=nexts[j];
nexts[++i]=++j;
}
}
int kmp(char x[],char y[])
{
int n=strlen(y);int m=strlen(x);
int i,j;
i=0;j=0;
while(i<n)
{
while(j!=-1&&x[j]!=y[i]) j=nexts[j];
++i;++j;
if(j>=m) return m;
}
return 0;
}
int get_index(int flag)
{
int i,j,k;
i=k=0;j=1;
int n;n=strlen(s);
while(i<n&&j<n&&k<n)
{
if(s[(i+k)%n]==s[(j+k)%n]) k++;
else if(flag==0)
{
if(s[(i+k)%n]>s[(j+k)%n]) i=i+k+1;
else j=j+k+1;
if(i==j) j++;k=0;
}
else if(flag==1)
{
if(s[(i+k)%n]>s[(j+k)%n]) j=j+k+1;
else i=i+k+1;
if(i==j) j++;k=0;
}
}
return min(i+1,j+1);
}
int main()
{
while(scanf("%s",s)!=EOF)
{
getnext(s);
int n=strlen(s);
int small,big,st,bt;
int len=n-nexts[n];
small=get_index(0);big=get_index(1);
st=bt=n/len;
printf("%d %d %d %d\n",small,st,big,bt);
}
return 0;
}
Finding Palindromes
解:容我说一句,这题真**沙比,oj是真的不行。无缘无故报re。写得我最心累的一题。
EXKMP+Trie
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=2e6+7;
char s[maxn];
char t[maxn];
int nexts[maxn];
int extend[maxn];
int pv[2][maxn];
int len[maxn];
void getnext(char x[],int nexts[],int lenx)
{
nexts[0]=lenx;
int j=0;
while(j+1<lenx&&x[j]==x[j+1]) j++;
nexts[1]=j;
int k=1;
for(int i=2;i<lenx;i++)
{
int P=nexts[k]+k-1;
int L=nexts[i-k];
if(L+i<P+1) nexts[i]=L;
else
{
j=max(0,P-i+1);
while(i+j<lenx&&x[j]==x[i+j]) j++;
nexts[i]=j;
k=i;
}
}
}
void exkmp(char x[],int lenx,char y[],int leny,int nexts[],int extend[],int L,int flag)
{
getnext(x,nexts,lenx);
int j=0;
while(j<lenx&&j<leny&&x[j]==y[j]) j++;
extend[0]=j;
int k=0;
for(int i=1;i<leny;i++)
{
int P=extend[k]+k-1;
int Ls=nexts[i-k];
if(i+Ls<P+1) extend[i]=Ls;
else
{
j=max(0,P-i+1);
while(i+j<leny&&j<lenx&&y[i+j]==x[j]) j++;
extend[i]=j;
k=i;
}
}
for(int i=0;i<leny;i++)
{
pv[flag][i+L]=(i+extend[i]==leny);
//cout<<pv[flag][i+L]<<endl;
}
}
struct Trie
{
int ch[maxn][26];
int ed[maxn];
int value[maxn];
int sz;
void init()
{
memset(ch[0],0,sizeof ch[0]);
ed[0]=0;sz=0;
value[0]=0;
}
void add(char *s,int lens,int L)
{
int u=0;
for(int i=0;i<lens;i++)
{
int id=s[i]-'a';
value[u]+=pv[0][L+i];
if(!ch[u][id])
{
ch[u][id]=++sz;
memset(ch[sz],0,sizeof ch[sz]);
value[sz]=0;
ed[sz]=0;
}
u=ch[u][id];
}
ed[u]++;
}
int finds(char *s,int lens,int L)
{
int res=0;
int u=0;
for(int i=0;i<lens;i++)
{
int id=s[i]-'a';
u=ch[u][id];
if(!u) break;
if((i<lens-1&&pv[1][i+L+1])||i==lens-1)
{
res+=ed[u];
}
}
if(u) res+=value[u];
return res;
}
}trie;
int main()
{
int n;
while(~scanf("%d",&n))
{
int tot=0;
trie.init();
memset(pv,0,sizeof pv);
for(int i=1;i<=n;i++)
{
scanf("%d%s",&len[i],s+tot);
memcpy(t+tot,s+tot,len[i]);
reverse(t+tot,t+tot+len[i]);
exkmp(t+tot,len[i],s+tot,len[i],nexts,extend,tot,0);
exkmp(s+tot,len[i],t+tot,len[i],nexts,extend,tot,1);
trie.add(s+tot,len[i],tot);
tot+=len[i];
}
ll ans=0;
tot=0;
for(int i=1;i<=n;i++)
{
ans+=1ll*trie.finds(t+tot,len[i],tot);
tot+=len[i];
}
printf("%lld\n",ans);
}
return 0;
}
DNA Sequence
解:这道题有点神了。首次见到fail数组的另类考查吧。AC自动机+矩阵快速幂。先建立关于各个模式串的AC自动机,根据fail数组构建矩阵,因为fail数组保存的就是所有不符合要求的字符串的连接关系,由此建立矩阵。此处再用到一个结论就是节点i到节点j经过K次的方案数 正好就是矩阵的K次幂。
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=1010;
#define LL long long
const int mod=100000;
map<char,int>ma;
struct matrix
{
LL x[110][110];//使用时按照题目需要改变数组的大小,此为4*4的矩阵
};
matrix mutimatrix(matrix a,matrix b,int t)//该函数用于算两个矩阵相乘
{
matrix temp;
memset(temp.x,0,sizeof(temp.x));
for(int i=0;i<t;i++)
for(int j=0;j<t;j++)
for(int k=0;k<t;k++)
{
temp.x[i][j]+=a.x[i][k]*b.x[k][j];
temp.x[i][j]%=mod;
}
return temp;
}
matrix k_powmatrix(matrix a,LL n,int t)//矩阵快速幂,和之前的快速幂迭代写法差不多。
{
matrix temp;
memset(temp.x,0,sizeof(temp.x));
for(int i=0;i<t;i++)
temp.x[i][i]=1;
while(n)
{
if(n&1)
temp=mutimatrix(temp,a,t);
a=mutimatrix(a,a,t);
n>>=1;
}
return temp;
}
struct Trie
{
int nexts[maxn][4];
int fail[maxn],ed[maxn];
int root,L;
int newnode()
{
for(int i=0;i<4;i++)
nexts[L][i]=-1;
ed[L++]=0;
return L-1;
}
void init()
{
ma['A']=0;ma['C']=1;ma['G']=2;ma['T']=3;
L=0;root=newnode();
}
void inserts(char s[])
{
int now=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=ma[s[i]];
if(nexts[now][c]==-1)
{
nexts[now][c]=newnode();
}
now=nexts[now][c];
}
ed[now]=1;
}
void get_fail()
{
queue<int> q;
fail[root]=root;
for(int i=0;i<4;i++)
{
if(nexts[root][i]==-1)nexts[root][i]=root;
else
{
fail[nexts[root][i]]=root;
q.push(nexts[root][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
if(ed[fail[now]]!=0) ed[now]=1;
for(int i=0;i<4;i++)
{
if(nexts[now][i]==-1) nexts[now][i]=nexts[fail[now]][i];
else
{
fail[nexts[now][i]]=nexts[fail[now]][i];
q.push(nexts[now][i]);
}
}
}
}
int used[1200];
void query(char s[])
{
memset(used,0,sizeof used);
int len=strlen(s);
int now=root;
for(int i=0;i<len;i++)
{
int c=ma[s[i]];
now=nexts[now][c];
int tmp=now;
while(tmp!=root)
{
if(ed[tmp]!=0) used[ed[tmp]]++;
tmp=fail[tmp];
}
}
}
}trie;
char s[200];
int main()
{
trie.init();
int m;ll n;
scanf("%d %lld",&m,&n);
for(int i=1;i<=m;i++)
{
scanf("%s",s);
trie.inserts(s);
}
trie.get_fail();
matrix mt,res;
memset(mt.x,0,sizeof mt.x);
for(int i=0;i<trie.L;i++)
{
if(trie.ed[i]!=0) continue;
for(int j=0;j<4;j++)
{
if(trie.ed[trie.nexts[i][j]]!=0) continue;
mt.x[i][trie.nexts[i][j]]++;
}
}
res=k_powmatrix(mt,n,trie.L);
ll ans=0;
for(int i=0;i<trie.L;i++)
{
ans=(ans+res.x[0][i])%mod;
}
printf("%lld\n",ans);
return 0;
}
考研路茫茫――单词情结
解:这题也很神。和上题类似,ac自动机+矩阵快速幂。确认过眼神,是做不出的题。
本题的一个矩阵要构造成 才行。有点儿不理解,换个形式构造就不对了。
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=1010;
#define LL long long
struct Matrix
{
unsigned long long mat[40][40];
int n;
Matrix(){}
Matrix(int _n)
{
n=_n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
mat[i][j] = 0;
}
Matrix operator *(const Matrix &b)const
{
Matrix ret = Matrix(n);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
for(int k=0;k<n;k++)
ret.mat[i][j]+=mat[i][k]*b.mat[k][j];
return ret;
}
};
struct Trie
{
int nexts[maxn][26];
int fail[maxn],ed[maxn];
int root,L;
int newnode()
{
for(int i=0;i<26;i++)
nexts[L][i]=-1;
ed[L++]=0;
return L-1;
}
void init()
{
L=0;root=newnode();
}
void inserts(char s[])
{
int now=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(nexts[now][c]==-1)
{
nexts[now][c]=newnode();
}
now=nexts[now][c];
}
ed[now]=1;
}
void get_fail()
{
queue<int> q;
fail[root]=root;
for(int i=0;i<26;i++)
{
if(nexts[root][i]==-1)nexts[root][i]=root;
else
{
fail[nexts[root][i]]=root;
q.push(nexts[root][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
if(ed[fail[now]]!=0) ed[now]=1;
for(int i=0;i<26;i++)
{
if(nexts[now][i]==-1) nexts[now][i]=nexts[fail[now]][i];
else
{
fail[nexts[now][i]]=nexts[fail[now]][i];
q.push(nexts[now][i]);
}
}
}
}
// int used[1200];
// void query(char s[])
// {
// memset(used,0,sizeof used);
// int len=strlen(s);
// int now=root;
// for(int i=0;i<len;i++)
// {
// int c=s[i]-'a';
// now=nexts[now][c];
// int tmp=now;
// while(tmp!=root)
// {
// if(ed[tmp]!=0) used[ed[tmp]]++;
// tmp=fail[tmp];
// }
// }
// }
}trie;
char s[110];
int main()
{
int N;ll len;
while(scanf("%d %lld",&N,&len)!=EOF)
{
trie.init();
for(int i=1;i<=N;i++)
{
scanf("%s",s);
trie.inserts(s);
}
trie.get_fail();
Matrix res1,E;
res1=Matrix(2);
res1.mat[0][0]=26;res1.mat[1][1]=res1.mat[1][0]=1;
E=Matrix(2);
E.mat[0][0]=E.mat[1][1]=1;
ll tmp=len;
while(tmp)
{
if(tmp&1) E=E*res1;
res1=res1*res1;
tmp>>=1;
}
unsigned long long ans1=E.mat[0][0]+E.mat[1][0];
ans1--;
res1=Matrix(trie.L+1);
for(int i=0;i<trie.L;i++)
{
if(trie.ed[i]==1) continue;
for(int j=0;j<26;j++)
{
if(trie.ed[trie.nexts[i][j]]==1) continue;
res1.mat[i][trie.nexts[i][j]]++;
}
}
E=Matrix(trie.L+1);
for(int i=0;i<=trie.L;i++)
{
E.mat[i][i]=1;
res1.mat[i][trie.L]=1;
}
tmp=len;
while(tmp)
{
if(tmp&1) E=E*res1;
res1=res1*res1;
tmp>>=1;
}
unsigned long long ans2=0;
for(int i=0;i<E.n;i++) ans2+=E.mat[0][i];
ans2--;
unsigned long long ans=ans1-ans2;
printf("%llu\n",ans);
}
return 0;
}
Wireless Password
解:再次见识AC自动机的各种考法。AC自动机+DP。建立关于给定的模式串的AC自动机后,根据tried图,可以定义dp[i][j][k]为当长度为i,走到节点j时,单词情况为k的方案数。
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=110;
#define LL long long
#define mod 20090717
struct Trie
{
int nexts[maxn][26];
int fail[maxn],ed[maxn];
int root,L;
ll dp[30][110][1030];
int newnode()
{
for(int i=0;i<26;i++)
nexts[L][i]=-1;
ed[L++]=0;
return L-1;
}
void init()
{
L=0;root=newnode();
}
void inserts(char s[],int id)
{
int now=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(nexts[now][c]==-1)
{
nexts[now][c]=newnode();
}
now=nexts[now][c];
}
ed[now]|=(1<<id);
}
void get_fail()
{
queue<int> q;
fail[root]=root;
for(int i=0;i<26;i++)
{
if(nexts[root][i]==-1)nexts[root][i]=root;
else
{
fail[nexts[root][i]]=root;
q.push(nexts[root][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
if(ed[fail[now]]!=0) ed[now]|=ed[fail[now]];
for(int i=0;i<26;i++)
{
if(nexts[now][i]==-1) nexts[now][i]=nexts[fail[now]][i];
else
{
fail[nexts[now][i]]=nexts[fail[now]][i];
q.push(nexts[now][i]);
}
}
}
}
ll solve(int n,int m,int K)
{
memset(dp,0,sizeof dp);
dp[0][0][0]=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<L;j++)
{
for(int k=0;k<(1<<m);k++)
{
if(dp[i][j][k]>0)
for(int ch=0;ch<26;ch++)
{
int ni=i+1;int nj=nexts[j][ch];int nk=k|ed[nexts[j][ch]];
dp[ni][nj][nk]+=dp[i][j][k];
dp[ni][nj][nk]%=mod;
}
}
}
}
ll ans=0;
for(int i=0;i<(1<<m);i++)
{
if(__builtin_popcount(i)<K) continue;
for(int j=0;j<L;j++)
{
ans+=dp[n][j][i];
ans%=mod;
}
}
return ans;
}
}trie;
char s[110];
int main()
{
int N,M,K;
while(scanf("%d %d %d",&N,&M,&K)!=EOF)
{
if(N==0&&M==0&&K==0) break;
trie.init();
for(int i=0;i<M;i++)
{
scanf("%s",s);
trie.inserts(s,i);
}
trie.get_fail();
ll ans=trie.solve(N,M,K);
printf("%lld\n",ans);
}
return 0;
}
Ring
解:AC自动机+dp。dp[i][j]:长度为i走到节点j的最大价值。dp[i+1][j]=max(dp[i][j],dp[i][fa(j)]+ed[fa(j)])
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=2100;
#define LL long long
#define mod 20090717
//struct Matrix
//{
// long long mat[110][110];
// int n;
// Matrix(){}
// Matrix(int _n)
// {
// n=_n;
// for(int i=0;i<n;i++)
// for(int j=0;j<n;j++)
// mat[i][j] = 0;
// }
// Matrix operator *(const Matrix &b)const
// {
// Matrix ret = Matrix(n);
// for(int i=0;i<n;i++)
// for(int j=0;j<n;j++)
// for(int k=0;k<n;k++)
// ret.mat[i][j]+=mat[i][k]*b.mat[k][j];
// return ret;
// }
//};
int cost[110];
struct Trie
{
int nexts[maxn][26];
int fail[maxn],ed[maxn];
int root,L;
int dp[110][2100];
string path[110][2100];
int newnode()
{
for(int i=0;i<26;i++)
nexts[L][i]=-1;
ed[L++]=0;
return L-1;
}
void init()
{
L=0;root=newnode();
}
bool cmp(string s,string t)
{
if(t=="")return false;
if(s.size()>t.size()) return true;
if(s.size()<t.size()) return false;
return s>t;
}
void inserts(char s[],int id)
{
int now=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(nexts[now][c]==-1)
{
nexts[now][c]=newnode();
}
now=nexts[now][c];
}
ed[now]=cost[id];
}
void get_fail()
{
queue<int> q;
fail[root]=root;
for(int i=0;i<26;i++)
{
if(nexts[root][i]==-1)nexts[root][i]=root;
else
{
fail[nexts[root][i]]=root;
q.push(nexts[root][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
//if(ed[fail[now]]!=0) ed[now]|=ed[fail[now]];
for(int i=0;i<26;i++)
{
if(nexts[now][i]==-1) nexts[now][i]=nexts[fail[now]][i];
else
{
fail[nexts[now][i]]=nexts[fail[now]][i];
q.push(nexts[now][i]);
}
}
}
}
string solve(int n)
{
memset(dp,-1,sizeof dp);
dp[0][0]=0;int res=0;
for(int i=0;i<=n;i++)
for(int j=0;j<=L;j++)
path[i][j]="";
for(int i=0;i<=n;i++)
{
for(int j=0;j<L;j++)
{
if(dp[i][j]==-1) continue;
for(int k=0;k<26;k++)
{
int ni=i+1;int nj=nexts[j][k];
if(dp[ni][nj]<dp[i][j]+ed[nexts[j][k]])
{
dp[ni][nj]=dp[i][j]+ed[nexts[j][k]];
path[ni][nj]=path[i][j]+(char)(k+'a');
}
else if(dp[ni][nj]==dp[i][j]+ed[nexts[j][k]])
{
if(cmp(path[ni][nj],path[i][j]+(char)(k+'a')))
{
path[ni][nj]=path[i][j]+(char)(k+'a');
}
}
}
res=max(res,dp[i][j]);
//cout<<dp[i][j]<<" ";
}
//cout<<endl;
}
string ans="";
if(res==0) return ans;
for(int i=0;i<=n;i++)
{
for(int j=0;j<L;j++)
{
if(dp[i][j]==res)
{
if(ans=="") ans=path[i][j];
else if(cmp(ans,path[i][j])) ans=path[i][j];
}
}
}
return ans;
}
}trie;
char s[110][50];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int N,M;
scanf("%d %d",&N,&M);
trie.init();
for(int i=0;i<M;i++)
{
scanf("%s",s[i]);
//trie.inserts(s,i);
}
for(int i=0;i<M;i++) scanf("%d",&cost[i]);
for(int i=0;i<M;i++) trie.inserts(s[i],i);
trie.get_fail();
string ans=trie.solve(N);
cout<<ans<<'\n';
}
return 0;
}
DNA repair
解:说实话我还是不能独立的做出这种题。通过trie图,构造一个字符串,这个字符串不会包含任何模式串。dp[i][j]:表示当字符长度为i,走到节点j的最小修改数。dp[i][next[j][k]]=min(dp[i][j],dp[i-1][j]+(s[i]!=k);
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=2100;
#define LL long long
#define mod 20090717
#define inf 0x3f3f3f3f
char s[1100];
struct Trie
{
int nexts[maxn][26];
int fail[maxn],ed[maxn];
int root,L;
int dp[1010][1010];
map<char,int>ma;
char num[4];
int newnode()
{
for(int i=0;i<4;i++)
nexts[L][i]=-1;
ed[L++]=0;
return L-1;
}
void init()
{
L=0;root=newnode();
ma['A']=0;ma['C']=1;ma['G']=2;ma['T']=3;
num[0]='A';num[1]='C';num[2]='G';num[3]='T';
}
bool cmp(string s,string t)
{
if(t=="")return false;
if(s.size()>t.size()) return true;
if(s.size()<t.size()) return false;
return s>t;
}
void inserts(char s[])
{
int now=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=ma[s[i]];
if(nexts[now][c]==-1)
{
nexts[now][c]=newnode();
}
now=nexts[now][c];
}
ed[now]=1;
}
void get_fail()
{
queue<int> q;
fail[root]=root;
for(int i=0;i<4;i++)
{
if(nexts[root][i]==-1)nexts[root][i]=root;
else
{
fail[nexts[root][i]]=root;
q.push(nexts[root][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
if(ed[fail[now]]!=0) ed[now]=1;
for(int i=0;i<4;i++)
{
if(nexts[now][i]==-1) nexts[now][i]=nexts[fail[now]][i];
else
{
fail[nexts[now][i]]=nexts[fail[now]][i];
q.push(nexts[now][i]);
}
}
}
}
int solve(int n)
{
for(int i=0;i<=n;i++)
for(int j=0;j<=L;j++)
dp[i][j]=inf;
dp[0][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<L;j++)
{
if(dp[i-1][j]==inf) continue;
for(int k=0;k<4;k++)
{
if(ed[nexts[j][k]]==1) continue;
int ni=i;int nj=nexts[j][k];
dp[ni][nj]=min(dp[ni][nj],dp[i-1][j]+(s[i]!=num[k]));//当构造的串的当前位与给定的串的对应位不一样时就要加1表示改变。
}
}
}
int ans=inf;
for(int i=0;i<L;i++) ans=min(ans,dp[n][i]);
if(ans==inf) return -1;
return ans;
}
}trie;
int main()
{
int N;
int kcase=0;
while(scanf("%d",&N)!=EOF)
{
if(N==0) break;
trie.init();
for(int i=0;i<N;i++)
{
scanf("%s",s);
trie.inserts(s);
}
trie.get_fail();
scanf("%s",s+1);
int len=strlen(s+1);
int ans=trie.solve(len);
printf("Case %d: %d\n",++kcase,ans);
}
return 0;
}
Lost's revenge
解:还是AC自动机+DP。dp[i][j]:走到节点i,四个碱基的使用情况为j的最大数量。j表示各个碱基数量。可以使用哈希的形式表示这所有的情况。dp[nexts[i][ch]][S1]=max(dp[i][S2]+ed[nexts[i][ch]],dp[nexts[i][ch]][S1])
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=2100;
#define LL long long
#define mod 20090717
#define inf 0x3f3f3f3f
char s[1100];
struct Trie
{
int nexts[maxn][26];
int fail[maxn],ed[maxn];
int root,L;
int dp[600][15000];
int hashs[45][45][45][45];
int cnt;
//map<char,int>ma;
int num[4];
int newnode()
{
for(int i=0;i<4;i++)
nexts[L][i]=-1;
ed[L++]=0;
return L-1;
}
void init()
{
L=0;root=newnode();
//ma['A']=0;ma['C']=1;ma['G']=2;ma['T']=3;
//num[0]='A';num[1]='C';num[2]='G';num[3]='T';
num[0]=num[1]=num[2]=num[3]=0;
cnt=0;
}
int ma(char x)
{
if(x=='A') return 0;
if(x=='C') return 1;
if(x=='G') return 2;
if(x=='T') return 3;
}
bool cmp(string s,string t)
{
if(t=="")return false;
if(s.size()>t.size()) return true;
if(s.size()<t.size()) return false;
return s>t;
}
void inserts(char s[])
{
int now=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=ma(s[i]);
if(nexts[now][c]==-1)
{
nexts[now][c]=newnode();
}
now=nexts[now][c];
}
ed[now]++;
}
void get_fail()
{
queue<int> q;
fail[root]=root;
for(int i=0;i<4;i++)
{
if(nexts[root][i]==-1)nexts[root][i]=root;
else
{
fail[nexts[root][i]]=root;
q.push(nexts[root][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
if(ed[fail[now]]!=0) ed[now]+=ed[fail[now]];
for(int i=0;i<4;i++)
{
if(nexts[now][i]==-1) nexts[now][i]=nexts[fail[now]][i];
else
{
fail[nexts[now][i]]=nexts[fail[now]][i];
q.push(nexts[now][i]);
}
}
}
}
int solve(int n)
{
memset(dp,-1,sizeof dp);
for(int i=1;i<=n;i++)
num[ma(s[i])]++;
for(int i=0;i<=num[0];i++)
for(int j=0;j<=num[1];j++)
for(int k=0;k<=num[2];k++)
for(int h=0;h<=num[3];h++)
hashs[i][j][k][h]=cnt++;
dp[0][0]=0;
for(int i=0;i<=num[0];i++)
{
for(int j=0;j<=num[1];j++)
{
for(int k=0;k<=num[2];k++)
{
for(int h=0;h<=num[3];h++)
{
if(i+j+k+h==0) continue;
for(int ti=0;ti<L;ti++)
{
for(int ch=0;ch<4;ch++)
{
int tmp1=hashs[i][j][k][h];
int tmp2;
if(ch==0){
if(i==0) continue;
else tmp2=hashs[i-1][j][k][h];
}
else if(ch==1)
{
if(j==0) continue;
else tmp2=hashs[i][j-1][k][h];
}
else if(ch==2)
{
if(k==0) continue;
else tmp2=hashs[i][j][k-1][h];
}
else
{
if(h==0) continue;
else tmp2=hashs[i][j][k][h-1];
}
if(dp[ti][tmp2]==-1) continue;
dp[nexts[ti][ch]][tmp1]=max(dp[ti][tmp2]+ed[nexts[ti][ch]],dp[nexts[ti][ch]][tmp1]);
}
}
}
}
}
}
int res=0;int st=hashs[num[0]][num[1]][num[2]][num[3]];
for(int i=0;i<L;i++) res=max(res,dp[i][st]);
return res;
}
}trie;
int main()
{
int N;
int kcase=0;
while(scanf("%d",&N)!=EOF)
{
if(N==0) break;
trie.init();
for(int i=0;i<N;i++)
{
scanf("%s",s);
trie.inserts(s);
}
trie.get_fail();
scanf("%s",s+1);
int len=strlen(s+1);
int ans=trie.solve(len);
printf("Case %d: %d\n",++kcase,ans);
}
return 0;
}
Resource Archiver
解:这题就比之前的更牛逼一点了。AC自动机+DP+BFS。确实是想不出,到了比赛这种题也是没时间去做的。把给出来的好串和坏串都加入到AC自动机的字典数里。由于内存不允许,不能直接定义dp[i][j]表示走到节点i时构造的字符串的包含好串的情况为j。但可以发现,n远小于m,即好的点远小于总数。将所有好串的尾节点重新编号,并且求出两两之间的最短距离。则dp[i][j]即表示走到好的节点i时的情况为j。dp[i][ j | ma[j] ]=min(dp[i][ j | ma[j] ] , dp[k][j]+cost[k][i]);
//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
#define ll long long
const int maxn=60000;
#define LL long long
#define mod 20090717
#define inf 0x3f3f3f3f
char s[1100];
struct Trie
{
int nexts[maxn][2];
int fail[maxn],ed[maxn],vir[maxn];
int root,L;
int dp[600][15000];
int dist[maxn];bool vis[maxn];
int tot;int ma[500];
int cost[550][550];
int newnode()
{
for(int i=0;i<2;i++)
nexts[L][i]=-1;
vir[L]=0;ed[L++]=0;
return L-1;
}
void init()
{
tot=0;
L=0;root=newnode();
}
void inserts(char s[],int id,int flag)
{
int now=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'0';
if(nexts[now][c]==-1)
{
nexts[now][c]=newnode();
}
now=nexts[now][c];
}
if(flag) vir[now]=1;
else ed[now]|=(1<<id);
}
void get_fail()
{
queue<int> q;
fail[root]=root;
for(int i=0;i<2;i++)
{
if(nexts[root][i]==-1)nexts[root][i]=root;
else
{
fail[nexts[root][i]]=root;
q.push(nexts[root][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
ed[now]|=ed[fail[now]];
vir[now]+=vir[fail[now]];
for(int i=0;i<2;i++)
{
if(nexts[now][i]==-1) nexts[now][i]=nexts[fail[now]][i];
else
{
fail[nexts[now][i]]=nexts[fail[now]][i];
q.push(nexts[now][i]);
}
}
}
}
void bfs(int st)
{
queue<int> q;
while(!q.empty()) q.pop();
memset(vis,0,sizeof vis);
for(int i=0;i<L;i++) dist[i]=inf;
q.push(ma[st]);
dist[ma[st]]=0;vis[ma[st]]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<2;i++)
{
int nex=nexts[now][i];
if(vis[nex])continue;
if(vir[nex]) continue;
dist[nex]=dist[now]+1;
vis[nex]=1;
q.push(nex);
}
}
for(int i=0;i<tot;i++)
{
cost[st][i]=dist[ma[i]];
}
}
void get_new_node()
{
ma[tot++]=0;
for(int i=0;i<L;i++)
{
if(ed[i]!=0) ma[tot++]=i;
}
for(int i=0;i<tot;i++)
{
bfs(i);
}
}
int solve(int n)
{
get_new_node();
for(int i=0;i<tot;i++)
for(int j=0;j<(1<<n);j++)
dp[i][j]=inf;
dp[0][0]=0;
for(int j=0;j<(1<<n);j++)
{
for(int i=0;i<tot;i++)
{
if(dp[i][j]>=inf) continue;
for(int k=0;k<tot;k++)
{
if(i==k) continue;
dp[k][j|ed[ma[k]]]=min(dp[k][j|ed[ma[k]]],dp[i][j]+cost[i][k]);
}
}
}
int res=inf;
for(int i=0;i<tot;i++) res=min(res,dp[i][(1<<n)-1]);
return res;
}
}trie;
int main()
{
int n,m;
while(scanf("%d %d",&n,&m)!=EOF)
{
if(n==0&&m==0) break;
trie.init();
for(int i=0;i<n;i++)
{
scanf("%s",s);
trie.inserts(s,i,0);
}
for(int i=0;i<m;i++)
{
scanf("%s",s);
trie.inserts(s,i,1);
}
trie.get_fail();
int ans=trie.solve(n);
printf("%d\n",ans);
}
return 0;
}
之前也没有怎么学过字符串的算法,终于在前几天学了AC自动机。在懂得差不多的原理之后(其实有些部分还是模糊的)。正所谓纸上得来终觉浅,还是要通过做题去发现自己还有哪些不懂以及这个算法它能怎么考。通过这几天的bin巨的专题抗压训练后可以发现,基本不会在比赛中会直接单独考查AC自动机的多模匹配的作用,更多的是在它通过fail数组构造得出的一个trie图上做文章。遇到的类型有:①给定一些模式串问能有多少种构造方法得到不包含给定串的字符串,这种题通常就是通过trie图,判断不可以走到模式串的结尾来构造一个矩阵,通过矩阵快速幂得到方案数。②其他的就是通过各种DP乱搞的方式,但有个规律是dp的其中一维是和trie图上的节点有关(在我做过的题看来是这样的)。