贴一下队友博客链接
1001 Blank(HDU 6578)
题意:用({0,1,2,3})四种数填长为n的空行,有m个形如([l,r,x])的限制表示([l,r])区间要出现x种数。
题解:这种题只会有两种做法,容斥或者dp,场上试图容斥了大概20分钟并没有什么想法,其实是存在很大问题的,因为容斥很难解决限制。考虑(O(n^4))的dp,(dp[i][j][k][l])表示排序后在填完前i位(0,1,2,3)最后出现的位置((i>j geq k geq l)),从而根据当前这一位填哪种数得到四种对应的转移,然后就套路地将限制放到右端点,对于不满足条件的状态直接置0,从而无法向后转移。这里主要麻烦的是转移时for循环里的取等,诸如某些没有出现过的数会出现(j=k=l=0)的情况,以及在清空数组时也要注意取等。然后用滚动数组滚动掉一维,从而空间复杂度降为(O(n^3))。
#include<bits/stdc++.h>
#define ls (x<<1)
#define rs (x<<1|1)
#define ll long long
#define pb push_back
#define mp make_pair
#define db double
#define pii pair<int,int>
using namespace std;
const int M=1e4+7;
const int N=100+7;
const int inf=1e9;
const int mod=998244353;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int dp[2][N][N][N];
int T;
int n,m;
vector<pii>vec[N];
int Mod(int &x){
if(x>=mod)x-=mod;
}
int main(){
T=read();
while(T--){
n=read();m=read();
int tmp;
for(int i=1;i<=n;i++)vec[i].clear();
int l,r;
for(int i=1;i<=m;i++){
l=read();r=read();tmp=read();
vec[r].pb(mp(l,tmp));
}
dp[0][0][0][0]=1;
for(int i=1,s=1;i<=n;i++,s^=1){
for(int j=0;j<=i;j++){
for(int k=0;k<=j;k++){
for(int p=0;p<=k;p++){
dp[s][j][k][p]=0;
}
}
}
for(int j=0;j<i;j++){
for(int k=0;k<=j;k++){
for(int p=0;p<=k;p++){
Mod(dp[s][j][k][p]+=dp[s^1][j][k][p]);
Mod(dp[s][i-1][j][p]+=dp[s^1][j][k][p]);
Mod(dp[s][i-1][k][p]+=dp[s^1][j][k][p]);
Mod(dp[s][i-1][j][k]+=dp[s^1][j][k][p]);
}
}
}
for(int j=0;j<i;j++){
for(int k=0;k<=j;k++){
for(int p=0;p<=k;p++){
for(int v=0;v<vec[i].size();v++){
int t1=vec[i][v].first,t2=vec[i][v].second;
if((j>=t1)+(k>=t1)+(p>=t1)!=t2-1){
dp[s][j][k][p]=0;break;
}
}
}
}
}
}
int tt=n&1;
int ans=0;
for(int j=0;j<n;j++){
for(int k=0;k<=j;k++){
for(int p=0;p<=k;p++){
Mod(ans+=dp[tt][j][k][p]);
}
}
}
cout<<ans%mod<<"
";
}
}
1005 Typewriter (HDU 6583)
题解:场上看五分钟就知道是个SAM+DP的题,最朴素的dp[i]表示建立前i个字符的最小花费,有两种转移(dp[i]=min(dp[i-1]+p,dp[j]+q),j)满足(S[j+1,i])是(S[1,j])的一个子串。然后我当时也没怎么管复杂度,就想着直接暴力在SAM上暴跳fail,然后这样的复杂度是(O(n sqrt n))的,我也不是特别清楚这样能不能做(不考虑会T的情况),场上至多也就是感觉有点单调性就不会了。。正确的做法是这样的,这个dp是有一定单调性的(可以这样认为如果(j)这个位置无法通过第二种转移到(i)那么一定无法转移到(i+1)),所以就考虑用后缀自动机维护这个位置(j)。如果(S[1,i-1])的SAM满足条件的位置有一个(S[i])的转移且这个(i-j<=i/2),那么直接转移然后维护转移后后缀自动机上的对应满足条件的最小的位置。否则只能插入第(j+1)个元素,然后更新满足条件的位置,一直到(j geq i)或者有这样的(S[i])转移时跳出循环。之后同样进行(S[i])的转移以及位置更新。复杂度(O(n))。
PS:其实我一直对SAM这样的线性没有一个明确的认识。。
#include<bits/stdc++.h>
#define ls (x<<1)
#define rs (x<<1|1)
#define ll long long
#define pb push_back
#define mp make_pair
#define db double
#define pii pair<int,int>
using namespace std;
const int M=1e4+7;
const int N=2e5+7;
const int inf=1e9;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
char s[N];
ll p,q;
struct SAM{
int last,tot,cnt,son[N<<1][26],len[N<<1],f[N<<1],pos[N<<1];
int size[N<<1];
ll val[N];
char S[N];
int n;
int cc;
int pl;
ll dp[N<<1];
void insert(int c){
int p=last,np=++tot;len[np]=len[last]+1;last=np;size[np]=1;
while(p&&!son[p][c])son[p][c]=np,p=f[p];
if(!p)f[np]=1;
else{
int q=son[p][c];
if(len[q]==len[p]+1)f[np]=q;
else{
int nq=++tot;len[nq]=len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
f[nq]=f[q];f[q]=f[np]=nq;
while(p&&son[p][c]==q)son[p][c]=nq,p=f[p];
}
}
}
void build(){
last=tot=1;pl=1;
n=strlen(s);
int l=1,r=0;
insert(s[0]-'a');
dp[0]=p;
for(int i=1;i<n;i++){
cc=s[i]-'a';
r++;
while((!son[pl][cc]||(r-l+1)*2>i+1)&&l<=r){
insert(s[l]-'a');l++;
while(pl&&len[f[pl]]>=(r-l))pl=f[pl];
if(!pl)pl=1;
}
pl=son[pl][cc];
if(!pl)pl=1;
while(pl&&len[f[pl]]>=(r-l+1))pl=f[pl];
if(!pl)pl=1;
dp[i]=dp[i-1]+p;
//cout<<22<<" "<<l<<"
";
if(l<=r){
dp[i]=min(dp[i],dp[l-1]+q);
}
}
//for(int i=0;i<n-1;i++)cout<<dp[i]<<" ";cout<<"
";
cout<<dp[n-1]<<"
";
for(int i=1;i<=tot;i++)memset(son[i],0,sizeof(son[i]));
}
}sam;
int main(){
while(scanf("%s",s)!=EOF){
p=read();q=read();
//cout<<p<<" "<<q<<"
";
sam.build();
}
}
1010 Kingdom (HDU 6587)
题意:给([1,n])二叉树的前序遍历和中序遍历(可能某些位置为0),问有多少种可能的形态。
题解:这题的前置芝士就是前序中序遍历的区间关系,然后根据4C那个题当时的写法,我们可以写出一种分治的形态,(dp[x][l1][r1][l2][r2])表示前序在([l1,r1])区间中序在([l2,r2])的对应前序的根节点在x的位置的方案数,然后其实这个[l1,r1]并没有什么用,所以可以变成3维(dp[x][l][r])表示对应中序在([l,r])区间上的方案数,那么答案就是(dp[1][1][n])。然而这样并不是答案,因为有一些位置是0,如果某些值在前序也没有出现过,中序也没有出现过,那么这些数字在确定的树的形态的情况下位置是任意的,所以还要乘上全排列数。然后就讨论几类转移就行,分为a[x]为0、a[x]不为0,a[x]是否在b中出现,b[i]是否在a中出现,这样进行讨论,要注意的是因为有一些情况是枚举a[x]在b中对应位置的,要维护b串中左子树的最大出现位置,从而判断是否满足转移限制,以及一些边界情况不能遗漏,比如左子树为空时,(l=r+1),这时dp的状态要设为1而不是0。(l=r)也要讨论,a[x]为0对应的b[l]必须相同才是合法的。总之细节挺多的,最容易遗漏(l=r+1)的情况以及a[x]在b中超出范围的情形。复杂度(O(n^3))。
#include<bits/stdc++.h>
#define ls (x<<1)
#define rs (x<<1|1)
#define ll long long
#define pb push_back
#define mp make_pair
#define db double
#define pii pair<int,int>
using namespace std;
const int maxn=111;
const int mod=998244353;
const ull P=233;
inline ll read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int dp[maxn][maxn][maxn];
int a[maxn],b[maxn],pa[maxn],pb[maxn];
void dfs(int x,int l,int r){
if(dp[x][l][r]!=-1)return ;
dp[x][l][r]=0;
if(l>r){
if(l==r+1)dp[x][l][r]=1;
return;
}
if(l==r){
dp[x][l][r]=1;
if(a[x]&&b[l]){
if(a[x]!=b[l])dp[x][l][r]=0;
}
else {
if(a[x]&&pb[a[x]]&&pb[a[x]]!=l)dp[x][l][r]=0;
if(b[l]&&pa[b[l]]&&pa[b[l]]!=x)dp[x][l][r]=0;
}
return ;
}
if(a[x]){
if(pb[a[x]]){
if(pb[a[x]]<l||pb[a[x]]>r)dp[x][l][r]=0;
else{
dfs(x+1,l,pb[a[x]]-1);
dfs(x+1+pb[a[x]]-l,pb[a[x]]+1,r);
dp[x][l][r]=1ll*dp[x+1][l][pb[a[x]]-1]*dp[x+1+pb[a[x]]-l][pb[a[x]]+1][r]%mod;
}
return;
}
int mx=l;
for(int i=l;i<=r;i++){
if(pa[b[i]]){
if(pa[b[i]]<x||pa[b[i]]>x+r-l)break;
mx=max(l,l+pa[b[i]]-x);
}
if(i<mx)continue;
if(b[i])continue;
dfs(x+1,l,i-1);
dfs(x+1+i-l,i+1,r);
dp[x][l][r]=(dp[x][l][r]+1ll*dp[x+1][l][i-1]*dp[x+1+i-l][i+1][r]%mod)%mod;
}
return;
}
int mx=l;
for(int i=l;i<=r;i++){
if(pa[b[i]]){
if(pa[b[i]]<x||pa[b[i]]>x+r-l)break;
mx=max(l,l+pa[b[i]]-x);
}
if(i<mx)continue;
dfs(x+1,l,i-1);
dfs(x+1+i-l,i+1,r);
dp[x][l][r]=(dp[x][l][r]+1ll*dp[x+1][l][i-1]*dp[x+1+i-l][i+1][r]%mod)%mod;
}
return;
}
int T;
int n;
int main(){
T=read();
while(T--){
n=read();
for(int i=1;i<=n;i++)a[i]=b[i]=pa[i]=pb[i]=0;
for(int i=1;i<=n;i++){
a[i]=read();if(a[i])pa[a[i]]=i;
}
for(int i=1;i<=n;i++){
b[i]=read();if(b[i])pb[b[i]]=i;
}
memset(dp,-1,sizeof(dp));
dfs(1,1,n);
ll ans=dp[1][1][n];
//cout<<ans<<"
";
int cnt=0;
for(int i=1;i<=n;i++)if(!pa[i]&&(!pb[i]))cnt++;
for(int i=1;i<=cnt;i++)ans=ans*i%mod;
cout<<ans<<"
";
}
}
1012 Sequence (HDU 6589)
题解:(b_i= sum _{j=i-kx}a_j),这是一个线性变换,所以可以考虑用多项式进行处理,(b_i=sum{a_i}x^isum{x^{ik}}),从而对于同一个k来说,只要将第二项求幂即可(考虑),然后就可以分别对k=1,2,3进行NTT就行。题解告诉我们这样做会T的,因n、m较大。然后第二项实际上是个生成函数,({(sum x^{ik})}^n)的第ik项系数即是隔板模型(C(n+i-1,n-1))(将i个物品分成n份),因此直接直接计算系数,然后对每个k只做一次NTT就行了。复杂度(O(nlogn))。
#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define ls (x<<1)
#define rs (x<<1|1)
#define db double
#define all(x) x.begin(),x.end()
#define ll long long
#define pll pair<ll,ll>
#define pii pair<int,int>
#define eps 1e-9
#define inf 0x3f3f3f3f
using namespace std;
const int N=2e6+7;
const int mod=998244353;
const double pi=acos(-1.0);
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int pr=3,pl=332748118;
const int phi=mod-1;
int n,m;
int A[N],B[N],R[N],C[N],a[N],b[N];
int len,lim;
int fac[N],ifac[N];
ll qpow(ll a,ll b)
{
ll res=1;
while(b){
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
void init(int n){
for(lim=1,len=0;lim<=n;lim<<=1,len++);
for(int i=0;i<lim;i++){
R[i]=(R[i>>1]>>1)|((i&1)<<(len-1));
}
}
void NTT(int *A,int op)
{
for(int i=0;i<lim;i++)if(i<R[i])swap(A[i],A[R[i]]);
for(int i=1;i<lim;i<<=1)
{
ll W=qpow(op==1?pr:pl,phi/(i<<1));
for(int p=(i<<1),j=0;j<lim;j+=p)
{
int w=1;
for(int k=0;k<i;k++,w=1ll*w*W%mod)
{
int x=A[j+k],y=1ll*w*A[j+i+k]%mod;
A[j+k]=(x+y)%mod;
A[j+i+k]=(x-y+mod)%mod;
}
}
}
if(op==-1){
ll inv=qpow(lim,mod-2);
for(int i=0;i<lim;i++)A[i]=1ll*inv*A[i]%mod;
}
}
void mul(int *a,int *b){
init(2*n);
for(int i=0;i<lim;i++)A[i]=B[i]=C[i]=0;
for(int i=0;i<n;i++)A[i]=a[i];
for(int i=0;i<n;i++)B[i]=b[i];
NTT(A,1);NTT(B,1);
for(int i=0;i<lim;i++)C[i]=1ll*A[i]*B[i]%mod;
NTT(C,-1);
for(int i=0;i<lim;i++){if(C[i]<0)C[i]+=mod;a[i]=C[i];}
//return C;
}
int cal(int n,int m){
if(n<m)return 0;
return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int T;
int ans;
int cnt[4];
int main()
{
fac[0]=ifac[0]=1;
for(int i=1;i<N;i++)fac[i]=1ll*fac[i-1]*i%mod;
ifac[N-1]=qpow(fac[N-1],mod-2);
for(int i=N-2;i>=1;i--)ifac[i]=1ll*(i+1)*ifac[i+1]%mod;
T=read();
int tmp;
while(T--){
n=read();m=read();
cnt[1]=cnt[2]=cnt[3]=0;
for(int i=0;i<n;i++)a[i]=read();
for(int i=0;i<m;i++){
tmp=read();cnt[tmp]++;
}
for(int i=1;i<=3;i++){
if(cnt[i]){
for(int j=0;j<n;j++)b[j]=0;
for(int j=0;1ll*i*j<n;j++){
b[i*j]=cal(cnt[i]+j-1,cnt[i]-1);
}
mul(a,b);
}
}
ll ans=0;
for(int i=0;i<n;i++)ans^=(1ll*(i+1)*a[i]);
cout<<ans<<"
";
}
}