2024寒假年后集训日记

2.14

闲话

  • 初中的一部分人进校了。
  • 晚上学校没安排晚饭,吃了两桶泡面应付了。

做题纪要

SP913 QTREE2 - Query on a tree II

  • LCA 板子。

    点击查看代码
    struct node
    {
    	ll nxt,to,w;
    }e[20002];
    ll head[20002],dep[20002],dis[20002],fa[20002][25],N,cnt=0;
    void add(ll u,ll v,ll w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    void dfs(ll x,ll father,ll w)
    {
    	fa[x][0]=father;
    	dep[x]=dep[father]+1;
    	dis[x]=dis[father]+w;
    	for(ll i=1;(1<<i)<=dep[x];i++)
    	{
    		fa[x][i]=fa[fa[x][i-1]][i-1];
    	}
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father) 
    		{
    			dfs(e[i].to,x,e[i].w);
    		}
    	}
    }
    ll lca(ll x,ll y)
    {
    	if(dep[x]>dep[y])
    	{
    		swap(x,y);
    	}
    	for(ll i=N;i>=0;i--)
    	{
    		if(dep[x]+(1<<i)<=dep[y])
    		{
    			y=fa[y][i];
    		}			
    	}
    	if(x==y) 
    	{
    		return x;
    	}
    	else
    	{
    		for(ll i=N;i>=0;i--)
    		{
    			if(fa[x][i]!=fa[y][i])
    			{
    				x=fa[x][i];
    				y=fa[y][i];
    			}
    		}
    		return fa[x][0];
    	}
    }
    ll query_fa(ll x,ll y,ll k)
    {	
    	ll rt=lca(x,y);
    	if(dep[x]-dep[rt]+1<=k)
    	{
    		k=dep[y]-dep[rt]+1-(k-(dep[x]-dep[rt]+1));
    		swap(x,y);
    	}
    	for(ll i=N;i>=0;i--)
    	{
    		if((1<<i)+1<=k)
    		{
    			x=fa[x][i];
    			k-=(1<<i);
    		}
    	}
    	return x;
    }
    int main()
    {
    	ll t,n,u,v,w,k,i,j;
    	string pd;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cin>>n;
    		N=log2(n)+1;
    		cnt=0;
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		memset(dep,0,sizeof(dep));
    		memset(dis,0,sizeof(dis));
    		memset(fa,0,sizeof(fa));
    		for(i=1;i<=n-1;i++)
    		{
    			cin>>u>>v>>w;
    			add(u,v,w);
    			add(v,u,w);
    		}
    		dfs(1,0,0);
    		while(cin>>pd)
    		{
    			if(pd=="DONE")
    			{
    				break;
    			}
    			if(pd=="DIST")
    			{
    				cin>>u>>v;
    				cout<<dis[u]+dis[v]-2*dis[lca(u,v)]<<endl;
    			}
    			if(pd=="KTH")
    			{
    				cin>>u>>v>>k;
    				cout<<query_fa(u,v,k)<<endl;
    			}
    		}
    	}
    	return 0;
    }
    

luogu P3216 [HNOI2011]数学作业

  • fn=Concatenate(n) 。状态转移方程为 fn=10log10n+1fn1+n

  • Fn=[fnn+11] ,容易有 Fn=[fnn+11]=[fn1n1]×[10log10n+100110011]=[fn2n11]×[10log10n+100110011]2=[fn3n21]×[10log10n+100110011]3==[f10log10n110log10n1]×[10log10n+100110011]n10log10n+1=F10log10n1×[10log10n+100110011]n10log10n+1

  • 处理出 i=1log10n+1[10i00110011] ,然后计算即可。

    点击查看代码
    #define ll __int128_t    
    struct Matrix
    {
    	ll ma[5][5];
    	Matrix()
    	{
    		memset(ma,0,sizeof(ma));
    	}
    }f,a;
    ll read()
    {
    	ll x=0,f=1;
    	char c=getchar();
    	while(c>'9'||c<'0')
    	{
    		if(c=='-')
    		{
    			f=-1;
    		}
    		c=getchar();
    	}
    	while('0'<=c&&c<='9')
    	{
    		x=x*10+c-'0';
    		c=getchar();
    	}
    	return x*f;
    }
    void write(ll x)
    {
    	if(x<0)
    	{
    		putchar('-');
    		x=-x;
    	}
    	if(x>9)
    	{
    		write(x/10);
    	}
    	putchar((x%10)+'0');
    }
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
    	Matrix c;
    	for(ll i=1;i<=n;i++)
    	{
    		for(ll j=1;j<=k;j++)
    		{
    			for(ll h=1;h<=m;h++)
    			{
    				c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
    			}
    		}
    	}
    	return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
    	Matrix ans;
    	for(ll i=1;i<=n;i++)
    	{
    		ans.ma[i][i]=1;
    	}
    	while(b>0)
    	{
    		if(b&1)
    		{
    			ans=mul(ans,a,n,n,n,p);
    		}
    		b>>=1;
    		a=mul(a,a,n,n,n,p);
    	}
    	return ans;
    }
    int main()
    {
    	ll b,p,n=1,m=3,k=3,i;
    	b=read();
    	p=read();
    	f.ma[1][1]=0;
    	f.ma[1][2]=f.ma[1][3]=1;
    	for(i=10;i<=b;i*=10)
    	{
    		a.ma[1][1]=i;
    		a.ma[1][2]=a.ma[1][3]=a.ma[2][3]=a.ma[3][1]=0;
    		a.ma[2][1]=a.ma[2][2]=a.ma[3][2]=a.ma[3][3]=1;
    		f=mul(f,qpow(a,i-1-i/10+1,p,m),n,m,k,p);
    	}
    	a.ma[1][1]=i;
    	a.ma[1][2]=a.ma[1][3]=a.ma[2][3]=a.ma[3][1]=0;
    	a.ma[2][1]=a.ma[2][2]=a.ma[3][2]=a.ma[3][3]=1;
    	f=mul(f,qpow(a,b-i/10+1,p,m),n,m,k,p);//最后一次进行特判
    	write(f.ma[1][1]);
    	return 0;
    }
    

2.15

闲话

  • 早上被冻醒了,起床后发现没关窗户,乐。
  • 上午 huge 去查宿,把没叠被子的叫回去整改了,故 HZOI2024 基本全军覆没。但是貌似 huge 把空调开了。
  • 下午又因为大课间被 field 调侃了。
  • 晚上 @Vsinger_洛天依 想问 miaomiao 能不能打 CF ,然后被拒绝了。但 miaomiao 同意了 4 机房众人打 CF

做题纪要

AT_dp_r Walk

  • 多倍经验: HDU2157 How many ways??

  • fi,j,k 表示从 i 经过 k 个点走到 j 的不同路径数量。状态转移方程为 fi,j,k=h=1ndish,j×fi,h,k1

  • Ft=[f1,1,tf1,2,tf1,n,tf2,1,tf2,2,tf2,n,tfn,1,tfn,2,tfn,n,t] ,容易有 Ft=[f1,1,tf1,2,tf1,n,tf2,1,tf2,2,tf2,n,tfn,1,tfn,2,tfn,n,t]=[f1,1,t1f1,2,t1f1,n,t1f2,1,t1f2,2,t1f2,n,t1fn,1,t1fn,2,t1fn,n,t1]×[dis1,1dis1,2dis1,ndis2,1dis2,2dis2,ndisn,1disn,2disn,n]=[f1,1,t2f1,2,t2f1,n,t2f2,1,t2f2,2,t2f2,n,t2fn,1,t2fn,2,t2s˙fn,n,t2]×[dis1,1dis1,2dis1,ndis2,1dis2,2dis2,ndisn,1disn,2disn,n]2=[f1,1,t3f1,2,t3f1,n,t3f2,1,t3f2,2,t3f2,n,t3fn,1,t3fn,2,t3fn,n,t3]×[dis1,1dis1,2dis1,ndis2,1dis2,2dis2,ndisn,1disn,2disn,n]3==[f1,1,0f1,2,0f1,n,0f2,1,0f2,2,0f2,n,0fn,1,0fn,2,0fn,n,0]×[dis1,1dis1,2dis1,ndis2,1dis2,2dis2,ndisn,1disn,2disn,n]t=F0×[dis1,1dis1,2dis1,ndis2,1dis2,2dis2,ndisn,1disn,2disn,n]t

    点击查看代码
    const ll p=1000000007;
    struct Matrix
    {
    	ll ma[60][60];
    	Matrix()
    	{
    		memset(ma,0,sizeof(ma));
    	}
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
    	Matrix c;
    	for(ll i=1;i<=n;i++)
    	{
    		for(ll j=1;j<=k;j++)
    		{
    			for(ll h=1;h<=m;h++)
    			{
    				c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
    			}
    		}
    	}
    	return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
    	Matrix ans;
    	for(ll i=1;i<=n;i++)
    	{
    		ans.ma[i][i]=1;
    	}
    	while(b>0)
    	{
    		if(b&1)
    		{
    			ans=mul(ans,a,n,n,n,p);
    		}
    		b>>=1;
    		a=mul(a,a,n,n,n,p);
    	}
    	return ans;
    }
    int main()
    {
    	ll n,m,k,b,ans=0,i,j;
    	cin>>n>>b;
    	m=k=n;
    	for(i=1;i<=n;i++)
    	{
    		f.ma[i][i]=1;
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			cin>>a.ma[i][j];
    		}
    	}
    	f=mul(f,qpow(a,b,p,m),n,m,k,p);
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			ans=(ans+f.ma[i][j])%p;
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P4159 [SCOI2009] 迷路

  • fi,j,k 表示从 i 经过 k 秒走到 j 的不同路径数量。状态转移方程为 fi,j,k=h=1n[0kdish,j<k]×fi,h,kdish,j

  • 本题中 dish,j[0,9] ,考虑将其进行拆点。原第 x 个点拆成第 10(x1)+110(x1)+9 个点,每两个点之间连一条边权为 1 的边。另外,若原 x,y 之间有一条边权为 disx,y 的边,则将第 10(x1)+disx,y10(y1)+1 个点连一条边权为 1 的边。

    点击查看代码
    const int p=2009;
    struct Matrix
    {
    	int ma[105][105];
    	Matrix()
    	{
    		memset(ma,0,sizeof(ma));
    	}
    }f,a;
    Matrix mul(Matrix a,Matrix b,int n,int m,int k,int p)
    {
    	Matrix c;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=k;j++)
    		{
    			for(int h=1;h<=m;h++)
    			{
    				c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
    			}
    		}
    	}
    	return c;
    }
    Matrix qpow(Matrix a,int b,int p,int n)
    {
    	Matrix ans;
    	for(int i=1;i<=n;i++)
    	{
    		ans.ma[i][i]=1;
    	}
    	while(b>0)
    	{
    		if(b&1)
    		{
    			ans=mul(ans,a,n,n,n,p);
    		}
    		b>>=1;
    		a=mul(a,a,n,n,n,p);
    	}
    	return ans;   
    }
    int main()
    {   
    	int nn,b,n,m,k,i,j;
    	char pd;
    	cin>>nn>>b;
    	n=m=k=10*nn;
    	for(i=1;i<=nn;i++)
    	{
    		for(j=1;j<=nn;j++)
    		{
    			cin>>pd;
    			if(pd!='0')
    			{
    				a.ma[(i-1)*10+pd-'0'][(j-1)*10+1]=1;
    			}
    		}
    		for(j=1;j<=9;j++)
    		{
    			a.ma[(i-1)*10+j][(i-1)*10+j+1]=1;
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		f.ma[i][i]=1;
    	}
    	cout<<mul(f,qpow(a,b,p,m),n,m,k,p).ma[1][(nn-1)*10+1]<<endl;
    	return 0;
    }
    

luogu P2233 [HNOI2002] 公交车路线

  • fi,j 表示到达第 i 个公交站换了 j 次车的方案数,状态转移方程为 fi,j={f8,j1+f2,j1i=1f1,j1+f3,j1i=2f2,j1+f4,j1i=3f3,j1i=4f4,j1+f6,j1i=5f7,j1i=6f6,j1+f8,j1i=7f7,j1+f1,j1i=8

  • 滚动数组优化即可。

    点击查看代码
    const int p=1000;
    int f[10][2];
    int main()
    {
    	int n,i;
    	cin>>n;
    	f[1][0]=1;
    	for(i=1;i<=n;i++)
    	{
    		f[1][i%2]=(f[8][(i-1)%2]+f[2][(i-1)%2])%p;
    		f[2][i%2]=(f[1][(i-1)%2]+f[3][(i-1)%2])%p;
    		f[3][i%2]=(f[2][(i-1)%2]+f[4][(i-1)%2])%p;
    		f[4][i%2]=f[3][(i-1)%2]%p;
    		f[5][i%2]=(f[4][(i-1)%2]+f[6][(i-1)%2])%p;
    		f[6][i%2]=f[7][(i-1)%2]%p;
    		f[7][i%2]=(f[6][(i-1)%2]+f[8][(i-1)%2])%p;
    		f[8][i%2]=(f[7][(i-1)%2]+f[1][(i-1)%2])%p;
    	}
    	cout<<f[5][n%2]<<endl;
    	return 0;
    }
    

luogu P3193 [HNOI2008]GT考试

  • fi,j 表示准考证号枚举到第 i 位,不吉利数字匹配到第 j 位的方案数, gi,j 表示不吉利数字第 i 位后面增加一个数字后匹配到第 j 位的方案数。状态转移方程为 fi,j=k=0m1fi1,k×gk,j 。最终,有 i=0m1fn,i 即为所求。

  • gi,j 可以在 KMP 的过程中求出。

  • Fn=[fn,0fn,1fn,2fn,m1] ,容易有 Fn=[fn,0fn,1fn,2fn,m1]=[fn1,0fn1,1fn1,2fn1,m1]×[g0,0g0,1g0,2g0,m1g1,0g1,1g1,2g1,m1g2,0g2,1g2,2g2,m1gm1,0gm1,1gm1,2gm1,m1]=[fn2,0fn2,1fn2,2fn2,m1]×[g0,0g0,1g0,2g0,m1g1,0g1,1g1,2g1,m1g2,0g2,1g2,2g2,m1gm1,0gm1,1gm1,2gm1,m1]2=[fn3,0fn3,1fn3,2fn3,m1]×[g0,0g0,1g0,2g0,m1g1,0g1,1g1,2g1,m1g2,0g2,1g2,2g2,m1gm1,0gm1,1gm1,2gm1,m1]3==[f0,0f0,1f0,2f0,m1]×[g0,0g0,1g0,2g0,m1g1,0g1,1g1,2g1,m1g2,0g2,1g2,2g2,m1gm1,0gm1,1gm1,2gm1,m1]n=F0×[g0,0g0,1g0,2g0,m1g1,0g1,1g1,2g1,m1g2,0g2,1g2,2g2,m1gm1,0gm1,1gm1,2gm1,m1]n

    点击查看代码
    int nxt[25];
    char s[25];
    struct Matrix
    {
    	int ma[25][25];
    	Matrix()
    	{
    		memset(ma,0,sizeof(ma));
    	}
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
    	Matrix c;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=k;j++)
    		{
    			for(int h=1;h<=m;h++)
    			{
    				c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
    			}
    		}
    	}
    	return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
    	Matrix ans;
    	for(int i=1;i<=n;i++)
    	{
    		ans.ma[i][i]=1;
    	}
    	while(b>0)
    	{
    		if(b&1)
    		{
    			ans=mul(ans,a,n,n,n,p);
    		}
    		b>>=1;
    		a=mul(a,a,n,n,n,p);
    	}
    	return ans;
    }
    int main()
    {
    	int b,ans=0,n=1,m,k,p,i,j,h;
    	cin>>b>>m>>p>>(s+1);
    	k=m;
    	for(i=2,nxt[1]=j=0;i<=m;i++)
    	{
    		while(j>=1&&s[i]!=s[j+1])
    		{
    			j=nxt[j];
    		}
    		j+=(s[i]==s[j+1]);
    		nxt[i]=j;
    	}
    	for(i=0;i<=m-1;i++)
    	{
    		for(h='0';h<='9';h++)
    		{
    			j=i;
    			while(j>=1&&h!=s[j+1])
    			{
    				j=nxt[j];
    			}
    			j+=(h==s[j+1]);
    			a.ma[i+1][j+1]=(a.ma[i+1][j+1]+(j<=m-1))%p;
    		}
    	}
    	f.ma[1][0+1]=1;
    	f=mul(f,qpow(a,b,p,m),n,m,k,p);
    	for(i=0;i<=m-1;i++)
    	{
    		ans=(ans+f.ma[1][i+1])%p;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P3808 AC自动机(简单版)

  • 多倍经验: LibreOJ 10057. 「一本通 2.4 例 1」Keywords Search

  • AC 自动机板子。

    点击查看代码
    int trie[1000002][30],fail[1000002],sum[1000002],vis[1000002],tot=0;
    char s[1000002];
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	sum[x]++;
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];//通过修改字典树的结构来记录下一个能匹配的位置
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    int query(char s[],int len)
    {
    	int x=0,ans=0,i,j;
    	for(i=1;i<=len;i++)
    	{
    		x=trie[x][val(s[i])];
    		for(j=x;j!=0&&vis[j]!=1;j=fail[j])//暴力跳fail
    		{
    			ans+=sum[j];
    			vis[j]=1;//防止一条边重复走
    		}
    	}
    	return ans;
    }
    int main()
    {
    	int n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1));
    	}
    	build();
    	cin>>(s+1);
    	cout<<query(s,strlen(s+1))<<endl;
    	return 0;
    }
    

UVA644 Immediate Decodability

  • 多倍经验: SP4033 PHONELST - Phone List | UVA11362 Phone List

    点击查看代码
    int trie[100][3],sum[100],tot=0;
    char s[100][100];
    int val(char x)
    {
    	return x-'0';
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    		sum[x]++;
    	}
    }
    int find(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			return 0;
    		}
    		x=trie[x][val(s[i])];
    	}
    	return sum[x];
    }
    int main()
    {
    	int n=1,flag,i,cnt=0;
    	while(cin>>(s[n]+1))
    	{
    		tot=flag=0;
    		memset(trie,0,sizeof(trie));
    		memset(sum,0,sizeof(sum));
    		insert(s[n],strlen(s[n]+1));
    		n++;
    		while(cin>>(s[n]+1))
    		{
    			if(s[n][1]=='9')
    			{
    				n--;
    				break;
    			}
    			else
    			{
    				insert(s[n],strlen(s[n]+1));
    				n++;
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			if(find(s[i],strlen(s[i]+1))>1)
    			{
    				flag=1;
    				break;
    			}
    		}
    		n=1;
    		cnt++;
    		if(flag==0)
    		{
    			cout<<"Set "<<cnt<<" is immediately decodable"<<endl;
    		}
    		else
    		{
    			cout<<"Set "<<cnt<<" is not immediately decodable"<<endl;
    		}
    	}
    	return 0;
    }
    

luogu P3796 AC 自动机(简单版 II)

  • 多倍经验: UVA1449 Dominating Patterns

  • AC 自动机板子。

  • 记录 idi 表示节点 i 是输入的第几个字符串的末尾节点。然后进行转移即可。

    点击查看代码
    int trie[20000][30],fail[20000],sum[20000],id[20000],num[20000],tot=0;
    char s[160][80],t[1000002];
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len,int idx)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	id[x]=idx;
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    int query(char s[],int len)
    {
    	int x=0,ans=0,i,j;
    	for(i=1;i<=len;i++)
    	{
    		x=trie[x][val(s[i])];
    		for(j=x;j!=0;j=fail[j])
    		{
    			sum[j]++;
    		}
    	}
    	for(i=0;i<=tot;i++)
    	{
    		if(id[i]!=0)
    		{
    			ans=max(ans,sum[i]);
    			num[id[i]]=sum[i];
    		}
    	}
    	return ans;
    }
    int main()
    {
    	int n,i,ans;
    	while(cin>>n)
    	{
    		if(n==0)
    		{
    			break;
    		}
    		else
    		{
    			memset(trie,0,sizeof(trie));
    			memset(fail,0,sizeof(fail));
    			memset(sum,0,sizeof(sum));
    			memset(id,0,sizeof(id));
    			memset(num,0,sizeof(num));
    			tot=0;
    			for(i=1;i<=n;i++)
    			{
    				cin>>(s[i]+1);
    				insert(s[i],strlen(s[i]+1),i);
    			}
    			build();
    			cin>>(t+1);
    			ans=query(t,strlen(t+1));
    			cout<<ans<<endl;
    			for(i=1;i<=n;i++)
    			{
    				if(num[i]==ans)
    				{
    					cout<<(s[i]+1)<<endl;
    				}
    			}
    		}
    	}
    	return 0;
    }
    

luogu P5231 [JSOI2012]玄武密码

  • 将所有的 t 塞进一个 Trie 树里,并建立 AC 自动机。

  • 母串 sAC 自动机上跑一遍,并标记其所有的子串。

  • 对所有的 tTrie 树上找到一个最深的被标记的节点。

    • titj 都被标记了,且满足 i<j ,那么有 jTrie 树上比 i 更深,更容易对答案产生贡献。
    点击查看代码
    int trie[10000002][5],dep[10000002],fail[10000002],vis[10000002],tot=0;
    char s[100002][200],t[10000002];
    int val(char x)
    {
    	if(x=='E')
    	{
    		return 1;
    	}
    	if(x=='S')
    	{
    		return 2;
    	}
    	if(x=='W')
    	{
    		return 3;
    	}
    	if(x=='N')
    	{
    		return 4;
    	}
    	return 0;
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=4;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=4;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    void mark(char s[],int len)
    {
    	int x=0,i,j;
    	for(i=1;i<=len;i++)
    	{
    		x=trie[x][val(s[i])];
    		for(j=x;j!=0&&vis[j]!=1;j=fail[j])
    		{
    			vis[j]=1;
    		}
    	}
    }
    int query(char s[],int len)
    {
    	int x=0,ans=0,i;
    	for(i=1;i<=len;i++)
    	{   
    		x=trie[x][val(s[i])];
    		ans=max(ans,vis[x]*i);
    	}
    	return ans;
    }
    int main()
    {
    	int n,m,i;
    	cin>>n>>m>>(t+1);
    	for(i=1;i<=m;i++)
    	{
    		cin>>(s[i]+1);
    		insert(s[i],strlen(s[i]+1));
    	}
    	build();
    	mark(t,n);
    	for(i=1;i<=m;i++)
    	{
    		cout<<query(s[i],strlen(s[i]+1))<<endl;
    	}
    	return 0;
    }   
    

2.16

闲话

  • miaomiao 过来催了下做 AC 自动机的题。
  • 下午,初中进校的人差不多齐了。
  • 晚上在大操场跑步的时候看见 3,4 号楼连廊的 2,3 楼的备课区还亮着灯。

做题纪要

luogu P5357 【模板】AC 自动机

  • 拓扑排序优化 AC 自动机建图板子。

    • 前置知识
      • 一个 AC 自动机中若只保留 fail 边,那么剩余的图一定是一棵树。
      • AC 自动机中每个点的出度为 1 ,即其 fail 边。
    • 类似子树大小求和,通过打标记的形式来在 fail 树上进行转移。
    点击查看代码(不使用链式前向星)
    int trie[6000000][30],fail[6000000],flag[6000000],id[6000000],din[6000000],lazy[6000000],ans[6000000],tot=0;
    char s[6000000];
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len,int idx)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	flag[x]=(flag[x]==0)?idx:flag[x];
    	id[idx]=flag[x];
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{   
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				din[fail[trie[x][i]]]++;
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    void mark(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		x=trie[x][val(s[i])];
    		lazy[x]++;
    	}
    }
    void top_sort()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=tot;i++)
    	{
    		if(din[i]==0)
    		{
    			q.push(i);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		ans[flag[x]]=lazy[x];
    		lazy[fail[x]]+=lazy[x];//因只有一条出边,故可以不记录整张图,而用fail指针找到子节点
    		din[fail[x]]--;
    		if(din[fail[x]]==0)
    		{
    			q.push(fail[x]);
    		}
    	}
    }
    int main()
    {
    	int n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1),i);
    	}
    	build();
    	cin>>(s+1);
    	mark(s,strlen(s+1));
    	top_sort();
    	for(i=1;i<=n;i++)
    	{
    		cout<<ans[id[i]]<<endl;
    	}
    	return 0;
    }
    
    点击查看代码(使用链式前向星)
    struct node
    {
    	int nxt,to;
    }e[6000000];
    int head[6000000],trie[6000000][30],fail[6000000],flag[6000000],id[6000000],din[6000000],lazy[6000000],ans[6000000],tot=0,cnt=0;
    char s[6000000];
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len,int idx)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	flag[x]=(flag[x]==0)?idx:flag[x];
    	id[idx]=flag[x];
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{   
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				add(trie[x][i],fail[trie[x][i]]);
    				din[fail[trie[x][i]]]++;
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    void mark(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		x=trie[x][val(s[i])];
    		lazy[x]++;
    	}
    }
    void top_sort()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=tot;i++)
    	{
    		if(din[i]==0)
    		{
    			q.push(i);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		ans[flag[x]]=lazy[x];
    		for(i=head[x];i!=0;i=e[i].nxt)
    		{
    			lazy[e[i].to]+=lazy[x];
    			din[e[i].to]--;
    			if(din[e[i].to]==0)
    			{
    				q.push(e[i].to);
    			}
    		}
    	}
    }
    int main()
    {
    	int n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1),i);
    	}
    	build();
    	cin>>(s+1);
    	mark(s,strlen(s+1));
    	top_sort();
    	for(i=1;i<=n;i++)
    	{
    		cout<<ans[id[i]]<<endl;
    	}
    	return 0;
    }
    

luogu P3121 [USACO15FEB]Censoring G

  • Trie 树上的节点记录是否是某个字符串的末尾,若成立则记录字符串的长度。

  • 删子串的过程类似 luogu P4824 [USACO15FEB] Censoring S

    点击查看代码
    int trie[3000000][30],fail[3000000],sum[3000000],f[3000000],tot=0;
    char s[3000000],t[3000000];
    deque<int>q;
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	sum[x]=len;
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    void mark(char s[],int len)
    {
    	int x=0,i,j;
    	for(i=1;i<=len;i++)
    	{
    		x=trie[x][val(s[i])];
    		q.push_back(i);
    		f[i]=x;
    		if(sum[x]!=0)
    		{
    			for(j=1;j<=sum[x];j++)
    			{
    				q.pop_back();
    			}
    			x=f[(q.empty()==0)?q.back():0];
    		}
    	}
    }
    int main()
    {
    	int n,i;
    	cin>>(t+1)>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1));
    	}
    	build();
    	mark(t,strlen(t+1));
    	while(q.empty()==0)
    	{
    		cout<<t[q.front()];
    		q.pop_front();
    	}
    	return 0;
    }
    

luogu P3966 [TJOI2013] 单词

  • 论文 T=s1+X+s2+X+s3+X++sn+X ,其中 X 为分隔符(区别于 az )。

    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[3000000];
    int head[3000000],trie[3000000][30],fail[3000000],flag[3000000],id[3000000],din[3000000],lazy[3000000],ans[3000000],tot=0,cnt=0;
    char s[3000000],t[3000000];
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len,int idx)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	flag[x]=(flag[x]==0)?idx:flag[x];
    	id[idx]=flag[x];
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				add(trie[x][i],fail[trie[x][i]]);
    				din[fail[trie[x][i]]]++;
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    void mark(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		x=trie[x][val(s[i])];
    		lazy[x]++;
    	}
    }
    void top_sort()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=tot;i++)
    	{
    		if(din[i]==0)
    		{
    			q.push(i);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		ans[flag[x]]=lazy[x];
    		for(i=head[x];i!=0;i=e[i].nxt) 
    		{
    			lazy[e[i].to]+=lazy[x];
    			din[e[i].to]--;
    			if(din[e[i].to]==0)
    			{
    				q.push(e[i].to);
    			}
    		}
    	}
    }
    int main()
    {
    	int n,lens,lent=0,i,j;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		lens=strlen(s+1);
    		insert(s,lens,i);
    		for(j=1;j<=lens;j++)
    		{
    			lent++;
    			t[lent]=s[j];
    		}
    		lent++;
    		t[lent]='{';
    	}
    	build();
    	mark(t,lent);
    	top_sort();
    	for(i=1;i<=n;i++)
    	{
    		cout<<ans[id[i]]<<endl;
    	}
    	return 0;
    }
    

luogu P2444 [POI2000] 病毒

  • 前置知识

    • AC 自动机是一个 Trie 图,可以存在环。
  • 考虑如何形成一个无限长的安全代码。我们发现,在 AC 自动机上找到一个合法的环,取这个环上任意位置进行破环为链,再进行复制即可得到一个无限长的安全代码。

    • 合法的环需满足环上的节点不经过病毒串的末尾节点和末尾节点所在的 fail 链上的节点。
      • 防止破环为链后形成病毒串。
  • 使用剪枝后的 DFS 找环。

    点击查看代码
    int trie[40000][3],fail[40000],vis[40000][4],tot=0;
    char s[40000];
    int val(char x)
    {
    	return x-'0';
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	vis[x][1]=1;
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=0;i<=1;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=0;i<=1;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{		
    				fail[trie[x][i]]=trie[fail[x]][i];
    				vis[trie[x][i]][1]|=vis[fail[trie[x][i]]][1];//继承标记
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    bool dfs(int x)
    {
    	vis[x][2]=vis[x][3]=1;//vis[x][3]用来剪枝
    	for(int i=0;i<=1;i++)
    	{
    		if(vis[trie[x][i]][1]==0)
    		{
    			if(vis[trie[x][i]][2]==1||(vis[trie[x][i]][3]==0&&dfs(trie[x][i])==true))
    			{
    				return true;
    			}
    		}
    	}
    	vis[x][2]=0;
    	return false;
    }
    int main()
    {
    	int n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1));
    	}
    	build();
    	if(dfs(0)==true)
    	{
    		cout<<"TAK"<<endl;
    	}
    	else
    	{
    		cout<<"NIE"<<endl;
    	}
    	return 0;
    }
    

AcWing 91. 最短Hamilton路径

  • fi,j 表示当前“点被经过的状态”对应的二进制数为 i ,且目前处于点 j 时的最短路径长度。状态转移方程为 fi,j=mink=0n1{[(ij)&10]×[((i(1j))k)&10]×(fi(1j),k+disk,j)}

    点击查看代码
    int dis[21][21],f[(1<<20)+1][21];
    int main()
    {
    	int n,i,j,k;
    	cin>>n;
    	memset(f,0x3f,sizeof(f));
    	f[1][0]=0;
    	for(i=0;i<=n-1;i++)
    	{
    		for(j=0;j<=n-1;j++)
    		{
    			cin>>dis[i][j];
    		}
    	}
    	for(i=1;i<=(1<<n)-1;i++)
    	{
    		for(j=0;j<=n-1;j++)
    		{
    			if((i>>j)&1)//路径i中包括当前点j才能进行转移
    			{
    				for(k=0;k<=n-1;k++)
    				{
    					if(((i-(1<<j))>>k)&1)//先将路径i中的j除去,再判断是否包括当前点k
    					{
    						f[i][j]=min(f[i][j],f[i-(1<<j)][k]+dis[k][j]);
    					}
    				}
    			}
    		}
    	}
    	cout<<f[(1<<n)-1][n-1]<<endl;
    	return 0;
    }
    

luogu P3041 [USACO12JAN]Video Game G

  • fi,j 表示前 i 个字符,当前运行到 AC 自动机的状态 j 时的最大得分。状态转移方程为 fi,k=maxkSon(j){fi1,j+sumk} ,其中 sumk 表示 fail 树上以 k 结尾的字符串个数。

  • 最终,有 maxi=0tot{fm,i} 即为所求。

    点击查看代码
    int trie[1000][5],fail[1000],sum[1000],f[2000][1000],tot=0;
    char s[1000];
    int val(char x)
    {
    	return x-'A'+1;
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	sum[x]++;
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=3;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=3;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				sum[trie[x][i]]+=sum[fail[trie[x][i]]];//继承标记
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    int main()
    {
    	int n,m,ans=0,i,j,k;
    	cin>>n>>m;
    	memset(f,-0x3f,sizeof(f));
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1));
    	}
    	build();
    	f[0][0]=0;
    	for(i=1;i<=m;i++)
    	{
    		for(j=0;j<=tot;j++)
    		{
    			if(f[i-1][j]>=0)//可以进行转移
    			{
    				for(k=1;k<=3;k++)
    				{
    					f[i][trie[j][k]]=max(f[i][trie[j][k]],f[i-1][j]+sum[trie[j][k]]);
    				}
    			}
    		}
    	}
    	for(i=0;i<=tot;i++)
    	{
    		ans=max(ans,f[m][i]);
    	}
    	cout<<ans<<endl;
    	return 0;
    }	
    

luogu P4052 [JSOI2007] 文本生成器

  • fi,j 表示前 i 个字符,当前运行到 AC 自动机的状态 j 时的不可读文本数量。状态转移方程为 fi,k=kSon(j)(1sumk)×fi1,j ,其中 sumk 表示 fail 树上是否存在以 k 结尾的字符串。

  • 最终,有 26mi=0totfm,i 即为所求。

    点击查看代码
    const ll p=10007;
    int trie[10000][30],fail[10000],vis[10000],f[200][10000],tot=0;
    char s[10000];
    int val(char x)
    {
    	return x-'A'+1;
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	vis[x]=1;
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				vis[trie[x][i]]|=vis[fail[trie[x][i]]];//继承标记
    				q.push(trie[x][i]);
    			}
    		}
    	}
    } 
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b>0)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    int main()
    {
    	int n,m,ans=0,i,j,k;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1));
    	}
    	build();
    	f[0][0]=1;
    	for(i=1;i<=m;i++)
    	{
    		for(j=0;j<=tot;j++)
    		{
    			for(k=1;k<=26;k++)
    			{
    				if(vis[trie[j][k]]==0)
    				{
    					f[i][trie[j][k]]=(f[i][trie[j][k]]+f[i-1][j])%p;
    				}
    			}
    		}
    	}
    	for(i=0;i<=tot;i++)
    	{
    		ans=(ans+f[m][i])%p;
    	}
    	cout<<(qpow(26,m,p)-ans+p)%p<<endl;
    	return 0;
    }
    

luogu P3414 SAC#1

  • i=0n[imod2=0](ni)=i=0n[imod2=1](ni)=2n1

    点击查看代码
    const ll p=6662333;
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b>0)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll n;
    	cin>>n;
    	cout<<qpow(2,n-1,p)<<endl;
    	return 0;
    }
    

luogu P1869 愚蠢的组合数

  • (nm) 为奇数当且仅当在二进制表示下 n 的每一个数位的数都不小于 m 的相应数位的数,即 n&m=m

    点击查看代码
    int main()
    {
    	int t,n,m,i;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		cout<<((n&m)==m)<<endl;
    	}
    	return 0;
    }
    

牛客 小白月赛87B 小苯的排序疑惑

  • 注意到 "严格小于" ,考虑最大程度上利用操作。

  • a1=mini=1n{ai} 时,选择 [2,n] 进行操作即可。

  • an=maxi=1n{ai} 时,选择 [1,n1] 进行操作即可。

  • 否则,选择 [1,n] 进行操作才能使得数组 a 按非降序排列,故无解。

    点击查看代码
    int a[200001];
    int main()
    {
    	int t,n,i,j,maxx,minn;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n;
    		maxx=0;
    		minn=0x7f7f7f7f;
    		for(j=1;j<=n;j++)
    		{
    			cin>>a[j];
    			maxx=max(maxx,a[j]);
    			minn=min(minn,a[j]);
    		}
    		if(a[1]==minn||a[n]==maxx)
    		{
    			cout<<"YES"<<endl;
    		}
    		else
    		{
    			cout<<"NO"<<endl;
    		}
    	}
    	return 0;
    }
    

牛客 小白月赛87C 小苯的IDE括号问题(easy)

  • 记录鼠标光标的左右字符,操作时仅对记录的左右字符进行更改即可。

    • 题解中称这种做法为双指针,但没有看出来哪里体现双指针了。
    点击查看代码
    char s[300000];
    string t;
    int main()
    {
    	int n,k,i,l,r;
    	cin>>n>>k>>(s+1);
    	for(i=1;i<=n;i++)
    	{
    		if(s[i]=='I')
    		{
    			l=i-1;
    			r=i+1;
    			break;
    		}
    	}
    	for(i=1;i<=k;i++)
    	{
    		cin>>t;
    		if(t=="backspace")
    		{
    			if(1<=l)
    			{
    				if(s[l]=='(')
    				{
    					if(r<=n)
    					{
    						if(s[r]==')')
    						{
    							r++;
    						}
    					}
    				}
    				l--;
    			}
    		}
    		if(t=="delete")
    		{
    			if(r<=n)
    			{
    				r++;
    			}
    		}
    	}
    	for(i=1;i<=l;i++)
    	{
    		cout<<s[i];
    	}
    	cout<<"I";
    	for(i=r;i<=n;i++)
    	{
    		cout<<s[i];
    	}
    	return 0;
    }
    

牛客 小白月赛87F 小苯的数组切分

  • 对于 & 操作,两个数一定不比一个数更优,故第三段最多只有一个数字 an

  • maxi=1n1{j=1iaj+(ai+1|ai+2|ai+3an1)+an} 即为所求。

    点击查看代码
    ll a[300000],sum2[300000];
    int main()
    {
    	ll n,sum1=0,sum3,ans=0,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	for(i=n-1;i>=2;i--)
    	{
    		sum2[i]=sum2[i+1]|a[i];
    	}
    	sum3=a[n];
    	for(i=2;i<=n-1;i++)
    	{
    		sum1^=a[i-1];
    		ans=max(ans,sum1+sum2[i]+sum3);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

牛客 小白月赛87A 小苯的石子游戏

  • 按照题意模拟即可。

    点击查看代码
    int a[200001];
    int main()
    {
    	int t,n,i,j,sum1,sum2;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n;
    		sum1=0;
    		sum2=0;
    		for(j=1;j<=n;j++)
    		{
    			cin>>a[j];
    		}
    		if(n%2==0)
    		{
    			for(j=1;j<=n;j++)
    			{
    				sum1+=(j%2==0)*a[j];
    				sum2+=(j%2==1)*a[j];
    			}
    		}
    		else
    		{
    			for(j=1;j<=n;j++)
    			{
    				sum1+=(j%2==1)*a[j];
    				sum2+=(j%2==0)*a[j];
    			}
    		}
    		if(sum1>sum2)
    		{
    			cout<<"Alice"<<endl;
    		}
    		else
    		{
    			cout<<"Bob"<<endl;
    		}
    	}
    	return 0;
    }
    

牛客 小白月赛87E 小苯的数组构造

  • 构造满足 {ai+biai1bi0 的最小 bi 即可。

    • a1 可能会为负数,故增加 a0=
    • 如果不加 bi0 的话,构造出的 bi 可能会有负数,导致极差变大。
    点击查看代码
    ll a[300000],b[300000];
    int main()
    {
    	ll n,i;
    	cin>>n;
    	a[0]=-0x3f3f3f3f;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		b[i]=(a[i-1]+b[i-1]>a[i])?a[i-1]+b[i-1]-a[i]:0;
    		cout<<b[i]<<" ";
    	}
    	return 0;
    }
    

牛客 小白月赛87D 小苯的IDE括号问题(hard)

  • deque 模拟即可。

    点击查看代码
    deque<char>l,r;
    char s[300000];
    string t;
    int main()
    {
    	int n,k,val,i;
    	cin>>n>>k>>(s+1);
    	for(i=1;i<=n;i++)
    	{
    		if(s[i]=='I')
    		{
    			val=i;
    			break;
    		}
    	}
    	for(i=1;i<=val-1;i++)
    	{
    		l.push_back(s[i]);
    	}
    	for(i=val+1;i<=n;i++)
    	{
    		r.push_back(s[i]);
    	}	
    	for(i=1;i<=k;i++)
    	{
    		cin>>t;
    		if(t=="backspace")
    		{
    			if(l.empty()==0)
    			{
    				if(l.back()=='(')
    				{
    					if(r.empty()==0)
    					{
    						if(r.front()==')')
    						{
    							r.pop_front();
    						}
    					}
    				}
    				l.pop_back();
    			}
    		}
    		if(t=="delete")
    		{
    			if(r.empty()==0)	
    			{
    				r.pop_front();
    			}
    		}
    		if(t=="<-")
    		{
    			if(l.empty()==0)
    			{
    				r.push_front(l.back());
    				l.pop_back();
    			}
    		}
    		if(t=="->")
    		{
    			if(r.empty()==0)
    			{
    				l.push_back(r.front());
    				r.pop_front();
    			}
    		}
    	}
    	while(l.empty()==0)
    	{
    		cout<<l.front();
    		l.pop_front();
    	}
    	cout<<"I";
    	while(r.empty()==0)
    	{
    		cout<<r.front();
    		r.pop_front();
    	}
    	return 0;
    }
    

每日总结、反思

  • 不要把题想得太复杂。

2.17

闲话

  • miaomiao 上午 8:0012:10 安排了一场模拟赛。
  • 中午睡觉的时候隔壁小孩一直在闹,没睡着觉。
  • 下午不知道什么原因,突然把全网开了,又突然关了。
  • 晚上临下课的时候, field 通知因周日早上有体活。
  • 晚上在大操场跑步的时候看见 3,4 号楼连廊的 2,3,4 楼的备课区还亮着灯。

做题纪要

luogu P2322 [HNOI2006] 最短母串问题

  • 通过 BFS 来实现依据长度和字典序来枚举所有可能的 T ,然后判断是否包含 Si

    • 由于是 BFS ,故第一次搜到的一定是最优答案。
  • 判断是否包含可以由 AC 自动机来实现。具体地,从 fax 转移到 x 时,会增加若干个在当前状态下不存在的字符串,其中增加可以用并集来实现,表示当前状态可以使用状态压缩来实现。

  • visx 表示 Trie 树上 x 节点包含的字符串的状态。在建 AC 自动机的同时进行转移。

    • 注意不保证 Si 两两不相等。
  • 需要记录路径。

    点击查看代码
    int trie[2000][30],fail[2000],path[9000000],vis[2000],tot=0;
    bool f[(1<<12)+1][2000];
    char s[2000],t[9000000];
    int val(char x)
    {
    	return x-'A'+1;
    }
    void insert(char s[],int len,int idx)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	vis[x]|=(1<<idx);//注意这里是或运算,防止出现多个字符串相等的情况
    }
    void build()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				vis[trie[x][i]]|=vis[fail[trie[x][i]]];//继承标记
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    int bfs(int n)
    {
    	int x,y,ans=0,len=0,i;
    	queue<pair<int,int> >q;
    	f[0][0]=true;
    	q.push(make_pair(0,0));
    	while(q.empty()==0)
    	{
    		x=q.front().first;
    		y=q.front().second;
    		q.pop();
    		if(y==(1<<n)-1)
    		{
    			break;
    		}
    		else
    		{
    			for(i=1;i<=26;i++)
    			{
    				if(f[y|vis[trie[x][i]]][trie[x][i]]==false)//因可能存在相等的字符串,故需防止多次搜到同一位置
    				{
    					f[y|vis[trie[x][i]]][trie[x][i]]=true;
    					q.push(make_pair(trie[x][i],y|vis[trie[x][i]]));
    					len++;
    					t[len]='A'+i-1;//枚举所有可能的T
    					path[len]=ans;
    				}
    			}
    			ans++;
    		}
    	}
    	return ans;
    }
    void print(int x)
    {
    	if(x==0)
    	{
    		return;
    	}
    	else
    	{
    		print(path[x]);
    		cout<<t[x];
    	}
    }
    int main()
    {
    	int n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1),i-1);
    	}
    	build();	
    	print(bfs(n));
    	return 0;
    }
    

BZOJ2905 背单词

  • 简化题意

    • t 组询问。
    • 每次询问给定 n 个字符串 S 及每个字符串的价值 wi
    • 求一个长度为 m(1mn) 的递增序列 p ,满足 {SpiSpi+1(1im1)pi[1,n](1im) ,且 i=1mwpi 最大。并输出此时的 i=1mwpi
  • 前置知识

    • i,j,k(ijk) 满足 SiSj,SjSk ,那么有 SiSk 。故当 Sj0 时,一定不对答案产生贡献。
    • SiSj ,则对于 Sj 存在一个前缀 Tfail 树上可以跳到 Si 的结束位置,等价于 fail 树上 Si 的结束位置是 Sj 某个前缀 T 的祖先,即 fail 树中 Sj 存在一个前缀 T 满足其在 Si 的结束位置的子树内。
  • 以下操作均忽略所有满足 wi0Si

  • fi 表示当前到第 i 个单词时,且选择了 Si 的最大价值。状态转移方程为 fi=maxj=1i1{[SjSi]×fj+wi}=maxj=1i1{[SjSi]×fj}+wi 。查询 max 和修改操作均可以用线段树维护失配树的 DFS 序优化。

    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[400000];
    struct SegmentTree
    {
    	int l,r;
    	ll maxx,lazy;
    }tree[1600000];
    int head[400000],trie[400000][30],fail[400000],w[400000],dfn[400000],out[400000],tot=0,cnt=0,num=0;
    char s[400000];
    vector<int>id[400000];
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len,int idx)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    		id[idx].push_back(x);//记录字符串每个字符所在的节点
    	}
    }
    void build_AC()
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    void dfs(int x)
    {
    	num++;
    	dfn[x]=num;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		dfs(e[i].to);
    	}
    	out[x]=num;
    }
    int lson(int x)
    {
    	return x*2;
    }
    int rson(int x)
    {
    	return x*2+1;
    }
    void pushup(int rt)
    {
    	tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx);
    }
    void build(int rt,int l,int r)
    {
    	tree[rt].l=l;
    	tree[rt].r=r;
    	tree[rt].maxx=tree[rt].lazy=0;
    	if(l==r)
    	{
    		tree[rt].maxx=0;
    		return;
    	}
    	int mid=(l+r)/2;
    	build(lson(rt),l,mid);
    	build(rson(rt),mid+1,r);
    	pushup(rt);
    }
    void pushdown(int rt)
    {
    	if(tree[rt].lazy!=0)
    	{
    		tree[lson(rt)].lazy=max(tree[lson(rt)].lazy,tree[rt].lazy);
    		tree[rson(rt)].lazy=max(tree[rson(rt)].lazy,tree[rt].lazy);
    		tree[lson(rt)].maxx=max(tree[lson(rt)].maxx,tree[rt].lazy);
    		tree[rson(rt)].maxx=max(tree[rson(rt)].maxx,tree[rt].lazy);
    		tree[rt].lazy=0;
    	}
    }
    void update(int rt,int l,int r,ll val)
    {
    	if(l<=tree[rt].r&&tree[rt].l<=r)
    	{
    		if(l<=tree[rt].l&&tree[rt].r<=r)
    		{
    			tree[rt].lazy=max(tree[rt].lazy,val);
    			tree[rt].maxx=max(tree[rt].maxx,val);
    			return;
    		}
    		pushdown(rt);
    		update(lson(rt),l,r,val);
    		update(rson(rt),l,r,val);
    		pushup(rt);
    	}
    }
    ll query(int rt,int l,int r)
    {
    	if(r<tree[rt].l||tree[rt].r<l)
    	{
    		return 0;
    	}
    	if(l<=tree[rt].l&&tree[rt].r<=r)
    	{
    		return tree[rt].maxx;
    	}
    	pushdown(rt);
    	return max(query(lson(rt),l,r),query(rson(rt),l,r));
    }
    int main()
    {
    	int t,n,i,j,k;
    	ll ans,maxx;
    	cin>>t;
    	for(k=1;k<=t;k++)
    	{
    		cin>>n;
    		tot=cnt=num=ans=0;
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		memset(tree,0,sizeof(tree));
    		memset(trie,0,sizeof(trie));
    		memset(fail,0,sizeof(fail));
    		memset(dfn,0,sizeof(dfn));
    		memset(out,0,sizeof(out));
    		for(i=1;i<=n;i++)
    		{
    			cin>>(s+1)>>w[i];
    			if(w[i]>0)
    			{
    				id[i].clear();
    				insert(s,strlen(s+1),i);
    			}
    		}
    		build_AC();
    		for(i=1;i<=tot;i++)//建fail树
    		{
    			add(fail[i],i);
    		}
    		dfs(0);
    		build(1,1,num);
    		for(i=1;i<=n;i++)
    		{
    			if(w[i]>0)
    			{
    				maxx=0;
    				for(j=0;j<id[i].size();j++)//跳fail
    				{
    					maxx=max(maxx,query(1,dfn[id[i][j]],dfn[id[i][j]]));//单点查询当区间查询写了
    				}
    				maxx+=w[i];
    				ans=max(ans,maxx);
    				update(1,dfn[id[i][id[i].size()-1]],out[id[i][id[i].size()-1]],maxx);//区间修改
    			}
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

tgHZOJ 5655. edit

tgHZOJ 5656. game

tgHZOJ 5657. score

tgHZOJ 5658. city

[ABC341A] Print 341

  • 依据题意模拟即可。

    点击查看代码
    int main()
    {
    	int n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cout<<"10";
    	}
    	cout<<"1";
    	return 0;
    }
    

[ABC341B] Foreign Exchange

  • 依据题意模拟即可。

    点击查看代码
    ll a[200001];
    int main()
    {
    	ll n,i,s,t;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>s>>t;
    		a[i+1]+=(a[i]/s)*t;
    	}
    	cout<<a[n]<<endl;
    	return 0;
    }
    

每日总结、反思

  • 读题的时候读全。
  • 需要尽快提升英语翻译能力。

2.18

闲话

  • 上午 huge 通知因有人来学校开会(貌似是在博艺馆),说我们要是遇到了来开会的人问我们是干什么的,不让我们说是信息奥赛的,说我们是特长生、体育特长生。难崩。
  • miaomiao 说这次我们应该把 AC 自动机写完了,让我们写二分答案。
  • 中午 @wkh2008@xrlong 在宿舍里用乒乓球排打羽毛球。
  • 下午大课间有两个高一的用羽毛球排打乒乓球。 @Pursuing_OIer 说接水的时候看见我班主任来机房了。
  • 临吃晚饭的时候, miaomiao 说了奥赛直升面试的事情,貌似来集训的都让去面试了(?)。说后续还是看 whk 和上次奥赛选拔的成绩。

做题纪要

[ABC341E] Alternating String

  • 一个串是 01 交替的,当且仅当任意相邻两位不相等。

  • 构造 xi=[sisi+1](1in1) 。若 [l,r]01 交替的,则有 i=lr1xi=rl

  • 操作 1 仅对 xl1,xr 进行修改,操作 2 仅对 i=lr1xi 进行查询。树状数组维护即可。

    点击查看代码
    int c[500010];
    string s;
    int lowbit(int x)
    {
    	return (x&(-x));
    } 
    int getsum(int x)
    {
    	int ans=0;
    	for(int i=x;i>0;i-=lowbit(i))
    	{
    		ans+=c[i];
    	}
    	return ans;
    } 
    void add(int n,int x,int key)
    {
    	for(int i=x;i<=n;i+=lowbit(i))
    	{
    		c[i]+=key;
    	}
    } 
    int main()
    {
    	int n,m,l,r,pd,i;
    	cin>>n>>m>>s;
    	s=" "+s;
    	for(i=1;i<s.size()-1;i++)
    	{
    		add(n,i,(s[i]!=s[i+1]));
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>pd>>l>>r;
    		if(pd==1)
    		{
    			if(l!=1)
    			{
    				add(n,l-1,(getsum(l-1)-getsum(l-2)==1)?-1:1);
    			}
    			if(r!=n)
    			{
    				add(n,r,(getsum(r)-getsum(r-1)==1)?-1:1);
    			}
    		}
    		if(pd==2)
    		{
    			if(getsum(r-1)-getsum(l-1)==r-l)
    			{
    				cout<<"Yes"<<endl;
    			}
    			else
    			{
    				cout<<"No"<<endl;
    			}
    		}
    	}
    	return 0;
    }
    

[ABC341G] Highest Ratio

  • 简化题意:给定一个长度为 n 的序列 a 。对于所有的 l(1ln) ,均求出 maxr=ln{i=lrairl+1}

  • sumi=j=1iaj,Pi=(i,sumi) ,则 maxr=ln{i=lrairl+1}=maxr=ln{sumrsuml1rl+1}=maxr=ln{Pr.yPl1.yPr.xPl1.x}

  • 从后往前用单调栈维护斜率即可。

    点击查看代码
    ll a[200002],sum[200002],s[200002],x[200002],y[200002],top=0;
    double ans[200002];
    double work(ll u,ll v)
    {
    	return 1.0*(y[v]-y[u])/(x[v]-x[u]);
    }
    int main()
    {
    	ll n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		sum[i]=sum[i-1]+a[i];
    		x[i]=i;
    		y[i]=sum[i];
    	}
    	top++;
    	s[top]=n;
    	for(i=n-1;i>=0;i--)
    	{
    		while(top>1&&work(i,s[top])<=work(s[top-1],s[top]))
    		{
    			top--;
    		}
    		ans[i+1]=work(i,s[top]);
    		top++;
    		s[top]=i;
    	}
    	for(i=1;i<=n;i++)
    	{
    		printf("%.8lf\n",ans[i]);
    	}
    	return 0;
    }
    

luogu P4045 [JSOI2009] 密码

  • 统计方案数比 luogu P2322 [HNOI2006] 最短母串问题 多了记录当搜到第一个不符合题意的字符串 T 时,以后搜到的所有字符串一定都不符合题意,需要及时退出枚举。广搜时转移方案数,

  • 记录路径时,记忆化搜索预处理出 gk,i,j 表示前 i 个字符串,当前运行到自动机的状态 j ,是否存在“字符串包含状态”所对应的二进制数为 k 。以此来进行剪枝,然后暴力 DFS 即可。

    点击查看代码
    __int128_t f[(1<<10)+1][30][300];//可能会爆long long
    ll trie[300][30],viss[(1<<10)+1][30][300],g[(1<<10)+1][30][300],fail[300],vis[300],tot=0;
    char s[30],t[30];
    ll val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],ll len,ll idx)
    {
    	ll x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	vis[x]|=(1<<idx);
    }
    void build()
    {
    	ll x,i;
    	queue<ll>q;
    	for(i=1;i<=26;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=26;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				vis[trie[x][i]]|=vis[fail[trie[x][i]]];
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    ll bfs(ll len,ll n)
    {
    	ll x,y,z,ans=0,i;
    	queue<pair<ll,pair<ll,ll> > >q;
    	f[0][0][0]=1;
    	q.push(make_pair(0,make_pair(0,0)));
    	while(q.empty()==0)
    	{
    		x=q.front().first;
    		y=q.front().second.first;
    		z=q.front().second.second;
    		q.pop();
    		if(z>len)
    		{
    			break;
    		}
    		if(y==(1<<n)-1&&z==len)
    		{
    			ans+=f[y][z][x];
    		}
    		else
    		{
    			for(i=1;i<=26;i++)
    			{
    				if(f[y|vis[trie[x][i]]][z+1][trie[x][i]]==0)
    				{
    					q.push(make_pair(trie[x][i],make_pair(y|vis[trie[x][i]],z+1)));
    				}
    				f[y|vis[trie[x][i]]][z+1][trie[x][i]]+=f[y][z][x];
    			}
    		}
    	}
    	return ans;
    }
    ll dfs(ll x,ll y,ll z,ll len,ll n)
    {
    	if(viss[y][z][x]==1)//记忆化搜索
    	{
    		return g[y][z][x];
    	}
    	else
    	{
    		viss[y][z][x]=1;
    		if(z==len)
    		{
    			g[y][z][x]=(y==((1<<n)-1));
    		}
    		else
    		{
    			for(ll i=1;i<=26;i++)
    			{
    				g[y][z][x]|=dfs(trie[x][i],y|vis[trie[x][i]],z+1,len,n);
    			}
    		}
    		return g[y][z][x];
    	}
    }
    void print(ll x,ll y,ll z,ll len)
    {
    	if(g[y][z][x]==1)
    	{
    		if(z==len)
    		{
    			cout<<(t+1)<<endl;
    		}
    		else
    		{
    			for(ll i=1;i<=26;i++)
    			{
    				t[z+1]=i+'a'-1;
    				print(trie[x][i],y|vis[trie[x][i]],z+1,len);
    			}
    		}
    	}
    }
    int main()
    {
    	ll len,n,ans,i;
    	cin>>len>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1),i-1);
    	}
    	build();
    	ans=bfs(len,n);
    	cout<<ans<<endl;
    	if(ans<=42)
    	{
    		dfs(0,0,0,len,n);
    		print(0,0,0,len);
    	}
    	return 0;
    }
    

UVA1636 Headshot

  • 统计环上子串 00 的个数为 a ,子串 0 的个数为 b 。不随机转动左轮的生存率为 aa+(ba)=ab ,随机转动的生存率为 bn

    点击查看代码
    char s[200];
    int main()
    {
    	int n,i,a,b;
    	while(cin>>(s+1))
    	{
    		n=strlen(s+1);
    		a=(s[1]=='0'&&s[n]=='0');
    		b=0;
    		for(i=1;i<=n;i++)
    		{
    			a+=(s[i-1]=='0'&&s[i]=='0');
    			b+=(s[i]=='0');
    		}
    		if(a*n<b*b)
    		{
    			cout<<"ROTATE"<<endl;
    		}
    		if(a*n==b*b)
    		{
    			cout<<"EQUAL"<<endl;
    		}
    		if(a*n>b*b)
    		{
    			cout<<"SHOOT"<<endl;
    		}
    	}
    	return 0;
    }
    

LightOJ 1104. Birthday Paradox

  • 设总人数为 m ,有 ==1Anmnm=1i=0m1nin0.5

  • 枚举求出 m 符合题意的最小正整数解即可。

  • 最终,有 m1 即为所求。

    点击查看代码
    int main()
    {
    	int t,n,m,i;
    	double sum;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n;
    		sum=1;
    		for(m=1;m<=n;m++)
    		{
    			sum*=1.0*(n-(m-1))/n;
    			if(1-sum>=0.5)
    			{
    				break;
    			}
    		}
    		cout<<"Case "<<i<<": "<<m-1<<endl;
    	}
    	return 0;
    }
    

牛客 牛客周赛 Round 33A 小红的单词整理

  • 按照题意模拟即可。

    点击查看代码
    int main()
    {
    	string a,b;
    	cin>>a>>b;
    	cout<<b<<endl;
    	cout<<a<<endl;
    	return 0;
    }
    

牛客 牛客周赛 Round 33B 小红煮汤圆

  • 按照题意模拟即可。

    点击查看代码
    int main()
    {
    	int n,x,k,ans=0,sum=0,num=0,i;
    	cin>>n>>x>>k;
    	ans=(n*x)/k;
    	cout<<ans<<endl;
    	for(i=1;i<=ans;i++)
    	{
    		sum=ceil(1.0*(k-num)/x);
    		cout<<sum<<" ";
    		num=sum*x+num-k;
    	}
    	return 0;
    }
    

牛客 牛客周赛 Round 33D 小红的数组清空

  • multiset 大法好。

    点击查看代码
    multiset<int>vis;
    stack<int>s;
    int main()
    {
    	int n,a,ans=0,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a;
    		vis.insert(a);
    	}
    	while(vis.empty()==0)
    	{
    		for(i=*vis.begin();vis.find(i)!=vis.end();i++)
    		{
    			s.push(i);
    		}
    		while(s.empty()==0)
    		{
    			if(vis.find(s.top())!=vis.end())
    			{
    				vis.erase(vis.find(s.top()));
    				s.pop();
    			}
    		}
    		ans++;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

牛客 牛客周赛 Round 33E 小红勇闯地下城

  • 最短路板子。

    点击查看代码
    struct node
    {
    	ll nxt,to,w;
    }e[500000];
    ll head[100010],dis[100010],vis[100010],cnt=0;
    void add(ll u,ll v,ll w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    ll val(ll i,ll j,ll m)
    {
    	return (i-1)*m+j;
    }
    void dijkstra(ll s,ll h)
    {
    	memset(dis,-0x3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	priority_queue<pair<ll,ll> >q;
    	ll x,i;
    	dis[s]=h;
    	q.push(make_pair(dis[s],s));
    	while(q.empty()==0)
    	{
    		x=q.top().second;
    		q.pop();
    		if(dis[s]>0)
    		{
    			if(vis[x]==0)
    			{
    				vis[x]=1;
    				for(i=head[x];i!=0;i=e[i].nxt)
    				{
    					if(dis[x]+e[i].w>0)
    					{
    						if(dis[e[i].to]<dis[x]+e[i].w)
    						{
    							dis[e[i].to]=dis[x]+e[i].w;
    							q.push(make_pair(dis[e[i].to],e[i].to));
    						}
    					}
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	ll q,n,m,h,s,t,k,i,j;
    	char pd;
    	cin>>q;
    	for(k=1;k<=q;k++)
    	{
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		cin>>n>>m>>h;
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=m;j++)
    			{
    				cin>>pd;
    				if(pd=='S')
    				{
    					s=val(i,j,m);
    					pd='0';
    				}
    				if(pd=='T')
    				{	
    					t=val(i,j,m);
    					pd='0';
    				}
    				if(1<=i-1)
    				{
    					add(val(i-1,j,m),val(i,j,m),-(pd-'0'));
    				}
    				if(i+1<=n)
    				{
    					add(val(i+1,j,m),val(i,j,m),-(pd-'0'));
    				}
    				if(1<=j-1)
    				{
    					add(val(i,j-1,m),val(i,j,m),-(pd-'0'));
    				}
    				if(j+1<=m)
    				{
    					add(val(i,j+1,m),val(i,j,m),-(pd-'0'));
    				}
    			}
    		}
    		dijkstra(s,h);
    		if(dis[t]>0)
    		{
    			cout<<"Yes"<<endl;
    		}
    		else
    		{
    			cout<<"No"<<endl;
    		}
    	}
    	return 0;
    }
    

2.19

闲话

  • miaomiao 上午 8:0012:10 安排了一场模拟赛。 @lxyt-415x@hh弟中弟@Joy_Dream_Glory 也来一起打了。面基到了 @Joy_Dream_Glory
  • 下午去面试了,时间大概是从 14:301600 。等的时候是在 @reach_the_top 所在的班里, @Pursuing_OIer 还把我笔落教学楼了,乐。
  • 晚上不知道为啥学校安排看电影, 1 机房看《长安三万里》, 2 机房看《飞驰人生 2》, feifei 骗我们说 4 机房没看电影。每人各分了一瓶易拉罐式的饮料、一小包薯片、一根火腿、一根香蕉、两个橘子。

做题纪要

tgHZOJ 2868. 谜之阶乘

tgHZOJ 1019. 小P的2048

2.20

闲话

  • 体活的时候下雪了,下得还挺大。
  • 下午放的状压 DP 视频。
  • 临吃饭的时候, miaomiao 告诉我们省选有体验名额,报名费每人 350 ,还说直升的名单没有定出来,但他已经把直升的人报上去了。
    • 貌似来集训的 miaomiao 没有通知是否能够直升,没来集训的 miaomiao 私信通知了。
  • 晚上熄灯后打羽毛球,被 huge 发现了,收了 @lty_ylzsx 的一个羽毛球拍和羽毛球,宿舍原住民的一个乒乓球拍。

做题纪要

tgHZOJ 2869. 子集

tgHZOJ 2870. 混凝土粉末

luogu P2719 搞笑世界杯

  • fi,j 表示买了 iA 类票, jB 类票时,最后两张票相同的概率。状态转移方程为 fi,j=fi1,j+fi,j12

    点击查看代码
    double f[2000][2000];
    int main()
    {
    	int n,i,j;
    	cin>>n;
    	for(i=2;i<=n/2;i++)
    	{
    		f[i][0]=f[0][i]=1;
    	}
    	for(i=1;i<=n/2;i++)
    	{
    		for(j=1;j<=n/2;j++)
    		{
    			f[i][j]=(f[i-1][j]+f[i][j-1])*0.5;
    		}
    	}
    	printf("%.4lf\n",f[n/2][n/2]);
    	return 0;
    }
    

luogu P7113 [NOIP2020] 排水系统

  • 直接拓扑排序即可。

    点击查看代码
    #define ll __int128_t
    struct node
    {
    	ll nxt,to;
    }e[600000];
    ll head[600000],f[600000],din[600000],dout[600000],fz[600000],fm[600000],cnt=0;
    ll read()
    {
    	ll x=0,f=1;
    	char c=getchar();
    	while(c>'9'||c<'0')
    	{
    		if(c=='-')
    		{
    			f=-1;
    		}
    		c=getchar();
    	}
    	while('0'<=c&&c<='9')
    	{
    		x=x*10+c-'0';
    		c=getchar();
    	}
    	return x*f;
    }
    void write(ll x)
    {
    	if(x<0)
    	{
    		putchar('-');
    		x=-x;
    	}
    	if(x>9)
    	{
    		write(x/10);
    	}
    	putchar((x%10)+'0');
    }
    void add(ll u,ll v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    ll gcd(ll a,ll b)
    {
    	return b?gcd(b,a%b):a;
    }
    ll lcm(ll a,ll b)
    {
    	return a/gcd(a,b)*b;
    }
    void top_sort(ll n)
    {	
    	queue<ll>q;
    	ll x,dd,i;
    	for(i=1;i<=n;i++)
    	{
    		if(din[i]==0)
    		{
    			fz[i]=1;
    			q.push(i);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=head[x];i!=0;i=e[i].nxt)
    		{
    			din[e[i].to]--;
    			dd=lcm(fm[x]*dout[x],fm[e[i].to]);
    			fz[e[i].to]=fz[e[i].to]*(dd/fm[e[i].to])+fz[x]*(dd/fm[x]/dout[x]);
    			fm[e[i].to]=dd;
    			dd=gcd(fz[e[i].to],fm[e[i].to]);
    			fz[e[i].to]/=dd;
    			fm[e[i].to]/=dd;
    			if(din[e[i].to]==0)
    			{
    				q.push(e[i].to);
    			}
    		}
    	}
    }
    int main()
    {
    	ll n,m,u,v,i,j;
    	n=read();
    	m=read();
    	for(i=1;i<=n;i++)
    	{
    		dout[i]=read();
    		u=i;
    		for(j=1;j<=dout[i];j++)
    		{
    			v=read();
    			add(u,v);
    			din[v]++;
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		fz[i]=0;
    		fm[i]=1;
    	}
    	top_sort(n);
    	for(i=1;i<=n;i++)
    	{
    		if(dout[i]==0)
    		{
    			write(fz[i]);
    			cout<<" ";
    			write(fm[i]);
    			cout<<endl;
    		}
    	}
    	return 0;
    }
    

tgHZOJ 2871. 排水系统

luogu P1471 方差

  • 推式子,有 x=i=1nxin,i=1n(xi+k)2=i=1nxi2+2xik+k2=i=1nxi2+2ki=1nxi+nk2,s2=i=1n(xix)2n=i=1nxi2n+nx2n2xi=1nxn=x2+x22x×x=x2x2=i=1nxi2n(i=1nxin)2 ,使用线段树维护区间和、区间的平方和即可。

    点击查看代码
    double a[500000];
    struct SegmentTree
    {
    	int l,r;
    	double sum1,sum2,lazy1,lazy2;
    }tree[500000];
    int lson(int x)
    {
    	return x*2;
    }
    int rson(int x)
    {
    	return x*2+1;
    }
    void pushup(int rt)
    {
    	tree[rt].sum1=tree[lson(rt)].sum1+tree[rson(rt)].sum1;
    	tree[rt].sum2=tree[lson(rt)].sum2+tree[rson(rt)].sum2;
    }
    void build(int rt,int l,int r)
    {
    	tree[rt].l=l;
    	tree[rt].r=r;
    	tree[rt].lazy1=tree[rt].lazy2=0;
    	if(l==r)
    	{
    		tree[rt].sum1=a[l];
    		tree[rt].sum2=a[l]*a[l];
    		return;
    	}
    	int mid=(l+r)/2;
    	build(lson(rt),l,mid);
    	build(rson(rt),mid+1,r);
    	pushup(rt);
    }
    void pushdown(int rt)
    {
    	if(tree[rt].lazy2!=0)
    	{
    		tree[lson(rt)].lazy2+=tree[rt].lazy2;
    		tree[rson(rt)].lazy2+=tree[rt].lazy2;
    		tree[lson(rt)].sum2+=2*tree[rt].lazy2*tree[lson(rt)].sum1+tree[rt].lazy2*tree[rt].lazy2*(tree[lson(rt)].r-tree[lson(rt)].l+1);
    		tree[rson(rt)].sum2+=2*tree[rt].lazy2*tree[rson(rt)].sum1+tree[rt].lazy2*tree[rt].lazy2*(tree[rson(rt)].r-tree[rson(rt)].l+1);
    		tree[rt].lazy2=0;
    	}
    	if(tree[rt].lazy1!=0)
    	{
    		tree[lson(rt)].lazy1+=tree[rt].lazy1;
    		tree[rson(rt)].lazy1+=tree[rt].lazy1;
    		tree[lson(rt)].sum1+=tree[rt].lazy1*(tree[lson(rt)].r-tree[lson(rt)].l+1);
    		tree[rson(rt)].sum1+=tree[rt].lazy1*(tree[rson(rt)].r-tree[rson(rt)].l+1);
    		tree[rt].lazy1=0;
    	}
    }
    void update(int rt,int l,int r,double val)
    {
    	if(l<=tree[rt].r&&tree[rt].l<=r)
    	{
    		if(l<=tree[rt].l&&tree[rt].r<=r)
    		{
    			tree[rt].lazy2+=val;
    			tree[rt].sum2+=2*val*tree[rt].sum1+val*val*(tree[rt].r-tree[rt].l+1);
    			tree[rt].lazy1+=val;
    			tree[rt].sum1+=val*(tree[rt].r-tree[rt].l+1);
    			return;
    		}
    		pushdown(rt);
    		update(lson(rt),l,r,val);
    		update(rson(rt),l,r,val);
    		pushup(rt);
    	}
    }
    double query1(int rt,int l,int r)
    {
    	if(r<tree[rt].l||tree[rt].r<l)
    	{
    		return 0;
    	}
    	if(l<=tree[rt].l&&tree[rt].r<=r)
    	{
    		return tree[rt].sum1;
    	}
    	pushdown(rt);
    	return query1(lson(rt),l,r)+query1(rson(rt),l,r);
    }
    double query2(int rt,int l,int r)
    {
    	if(r<tree[rt].l||tree[rt].r<l)
    	{
    		return 0;
    	}
    	if(l<=tree[rt].l&&tree[rt].r<=r)
    	{
    		return tree[rt].sum2;
    	}
    	pushdown(rt);
    	return query2(lson(rt),l,r)+query2(rson(rt),l,r);
    }
    int main()
    {
    	int n,m,x,y,pd,i;
    	double val,sum1,sum2;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	build(1,1,n);
    	for(i=1;i<=m;i++)
    	{
    		cin>>pd>>x>>y;
    		if(pd==1)
    		{
    			cin>>val;
    			update(1,x,y,val);
    		}
    		if(pd==2)
    		{
    			printf("%.4lf\n",query1(1,x,y)/(y-x+1));
    		}
    		if(pd==3)
    		{
    			sum1=query1(1,x,y)/(y-x+1);
    			sum2=query2(1,x,y)/(y-x+1);
    			printf("%.4lf\n",sum2-sum1*sum1);
    		}
    	}
    	return 0;
    }
    

2.21

闲话

  • miaomiao 上午 7:5011:20 安排了一场模拟赛。
  • 上午大课间后隔壁高一的貌似被抓去当苦力铲雪了。打模拟赛的众人幸免于难。
  • 下午因为昨晚上的事,狂 D 不止。
  • 晚上去吃饭的时候看见 little snowy 了。

做题纪要

luogu P4569 [BJWC2011] 禁忌

  • 对于一段固定的文本串,由于重叠的模式串不对伤害产生贡献,故考虑贪心,每碰到出现一个模式串将其分为一段,最终这个文本串的伤害就是划分次数。

  • fi,j 表示前 i 个字符,当前运行到 AC 自动机的 j 状态时受到的概率。状态转移方程为 {fi+1,k=j=0tot[kSon(j)]×[visk=0]×fi,j×1alphabetk0fi+1,0=j=0totk=0tot[kSon(j)]×[visk=1(k=0visk=0)]×fi,j×1alphabet ,其中 visk 表示 AC 自动机上是否存在以 k 结尾的字符串。另外,若 visk=1 ,则可能会受到一次伤害,有 ans=ans+fi,j×1alphabet

  • Ft=[ft,0ft,1ft,2ft,toti=1t1fi,0] ,容易有 Ft=[ft,0ft,1ft,2ft,totanst]=[ft1,0ft1,1ft1,2ft1,totanst1]×[i=0tot[iSon(0)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(0)]×[vis2=0]alphabet[totSon(0)]×[vistot=0]alphabeti=0tot[iSon(0)]×[visi=1]alphabeti=0tot[iSon(1)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(1)]×[vis2=0]alphabet[totSon(1)]×[vistot=0]alphabeti=0tot[iSon(1)]×[visi=1]alphabeti=0tot[iSon(2)]×[visi=1(i=0visi=0)]alphabet[1Son(2)]×[vis1=0]alphabet[2Son(2)]×[vis2=0]alphabet[totSon(2)]×[vistot=0]alphabeti=0tot[iSon(2)]×[visi=1]alphabeti=0tot[iSon(tot)]×[visi=1(i=0visi=0)]alphabet[1Son(tot)]×[vis1=0]alphabet[2Son(tot)]×[vis2=0]alphabet[totSon(tot)]×[vistot=0]alphabeti=0tot[iSon(tot)]×[visi=1]alphabet00001]=[ft2,0ft2,1ft2,2ft2,totanst2]×[i=0tot[iSon(0)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(0)]×[vis2=0]alphabet[totSon(0)]×[vistot=0]alphabeti=0tot[iSon(0)]×[visi=1]alphabeti=0tot[iSon(1)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(1)]×[vis2=0]alphabet[totSon(1)]×[vistot=0]alphabeti=0tot[iSon(1)]×[visi=1]alphabeti=0tot[iSon(2)]×[visi=1(i=0visi=0)]alphabet[1Son(2)]×[vis1=0]alphabet[2Son(2)]×[vis2=0]alphabet[totSon(2)]×[vistot=0]alphabeti=0tot[iSon(2)]×[visi=1]alphabeti=0tot[iSon(tot)]×[visi=1(i=0visi=0)]alphabet[1Son(tot)]×[vis1=0]alphabet[2Son(tot)]×[vis2=0]alphabet[totSon(tot)]×[vistot=0]alphabeti=0tot[iSon(tot)]×[visi=1]alphabet00001]2=[ft3,0ft3,1ft3,2ft3,totanst1]×[i=0tot[iSon(0)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(0)]×[vis2=0]alphabet[totSon(0)]×[vistot=0]alphabeti=0tot[iSon(0)]×[visi=1]alphabeti=0tot[iSon(1)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(1)]×[vis2=0]alphabet[totSon(1)]×[vistot=0]alphabeti=0tot[iSon(1)]×[visi=1]alphabeti=0tot[iSon(2)]×[visi=1(i=0visi=0)]alphabet[1Son(2)]×[vis1=0]alphabet[2Son(2)]×[vis2=0]alphabet[totSon(2)]×[vistot=0]alphabeti=0tot[iSon(2)]×[visi=1]alphabeti=0tot[iSon(tot)]×[visi=1(i=0visi=0)]alphabet[1Son(tot)]×[vis1=0]alphabet[2Son(tot)]×[vis2=0]alphabet[totSon(tot)]×[vistot=0]alphabeti=0tot[iSon(tot)]×[visi=1]alphabet00001]3==[f0,0f0,1f0,2f0,totans0]×[i=0tot[iSon(0)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(0)]×[vis2=0]alphabet[totSon(0)]×[vistot=0]alphabeti=0tot[iSon(0)]×[visi=1]alphabeti=0tot[iSon(1)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(1)]×[vis2=0]alphabet[totSon(1)]×[vistot=0]alphabeti=0tot[iSon(1)]×[visi=1]alphabeti=0tot[iSon(2)]×[visi=1(i=0visi=0)]alphabet[1Son(2)]×[vis1=0]alphabet[2Son(2)]×[vis2=0]alphabet[totSon(2)]×[vistot=0]alphabeti=0tot[iSon(2)]×[visi=1]alphabeti=0tot[iSon(tot)]×[visi=1(i=0visi=0)]alphabet[1Son(tot)]×[vis1=0]alphabet[2Son(tot)]×[vis2=0]alphabet[totSon(tot)]×[vistot=0]alphabeti=0tot[iSon(tot)]×[visi=1]alphabet00001]t=[10000]×[i=0tot[iSon(0)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(0)]×[vis2=0]alphabet[totSon(0)]×[vistot=0]alphabeti=0tot[iSon(0)]×[visi=1]alphabeti=0tot[iSon(1)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(1)]×[vis2=0]alphabet[totSon(1)]×[vistot=0]alphabeti=0tot[iSon(1)]×[visi=1]alphabeti=0tot[iSon(2)]×[visi=1(i=0visi=0)]alphabet[1Son(2)]×[vis1=0]alphabet[2Son(2)]×[vis2=0]alphabet[totSon(2)]×[vistot=0]alphabeti=0tot[iSon(2)]×[visi=1]alphabeti=0tot[iSon(tot)]×[visi=1(i=0visi=0)]alphabet[1Son(tot)]×[vis1=0]alphabet[2Son(tot)]×[vis2=0]alphabet[totSon(tot)]×[vistot=0]alphabeti=0tot[iSon(tot)]×[visi=1]alphabet00001]t=F0×[i=0tot[iSon(0)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(0)]×[vis2=0]alphabet[totSon(0)]×[vistot=0]alphabeti=0tot[iSon(0)]×[visi=1]alphabeti=0tot[iSon(1)]×[visi=1(i=0visi=0)]alphabet[1Son(0)]×[vis1=0]alphabet[2Son(1)]×[vis2=0]alphabet[totSon(1)]×[vistot=0]alphabeti=0tot[iSon(1)]×[visi=1]alphabeti=0tot[iSon(2)]×[visi=1(i=0visi=0)]alphabet[1Son(2)]×[vis1=0]alphabet[2Son(2)]×[vis2=0]alphabet[totSon(2)]×[vistot=0]alphabeti=0tot[iSon(2)]×[visi=1]alphabeti=0tot[iSon(tot)]×[visi=1(i=0visi=0)]alphabet[1Son(tot)]×[vis1=0]alphabet[2Son(tot)]×[vis2=0]alphabet[totSon(tot)]×[vistot=0]alphabeti=0tot[iSon(tot)]×[visi=1]alphabet00001]t

    点击查看代码
    struct Matrix
    {
    	long double ma[80][80];
    	Matrix()
    	{
    		memset(ma,0,sizeof(ma));
    	}
    }f,a;
    int trie[80][30],fail[80],vis[80],tot=0;
    char s[80];
    Matrix mul(Matrix a,Matrix b,int n,int m,int k)
    {
    	Matrix c;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=k;j++)
    		{
    			for(int h=1;h<=m;h++)
    			{
    				c.ma[i][j]=c.ma[i][j]+a.ma[i][h]*b.ma[h][j];
    			}
    		}
    	}
    	return c;
    }
    Matrix qpow(Matrix a,int b,int n)
    {
    	Matrix ans;
    	for(int i=1;i<=n;i++)
    	{
    		ans.ma[i][i]=1.0;
    	}
    	while(b>0)
    	{
    		if(b&1)
    		{
    			ans=mul(ans,a,n,n,n);
    		}
    		b>>=1;
    		a=mul(a,a,n,n,n);
    	}
    	return ans;
    }
    int val(char x)
    {
    	return x-'a'+1;
    }
    void insert(char s[],int len)
    {
    	int x=0,i;
    	for(i=1;i<=len;i++)
    	{
    		if(trie[x][val(s[i])]==0)
    		{
    			tot++;
    			trie[x][val(s[i])]=tot;
    		}
    		x=trie[x][val(s[i])];
    	}
    	vis[x]=1;
    }
    void build(long double alphabet)
    {
    	int x,i;
    	queue<int>q;
    	for(i=1;i<=alphabet;i++)
    	{
    		if(trie[0][i]!=0)
    		{
    			fail[trie[0][i]]=0;
    			q.push(trie[0][i]);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=1;i<=alphabet;i++)
    		{
    			if(trie[x][i]==0)
    			{
    				trie[x][i]=trie[fail[x]][i];
    			}
    			else
    			{
    				fail[trie[x][i]]=trie[fail[x]][i];
    				vis[trie[x][i]]|=vis[fail[trie[x][i]]];//继承标记
    				q.push(trie[x][i]);
    			}
    		}
    	}
    }
    int main()
    {
    	int b,nn,n,m,k,i,j;
    	long double alphabet;
    	cin>>nn>>b>>alphabet;
    	for(i=1;i<=nn;i++)
    	{
    		cin>>(s+1);
    		insert(s,strlen(s+1));
    	}
    	build(alphabet);
    	n=1;
    	m=k=tot+2;
    	f.ma[1][1]=a.ma[m][m]=1;
    	for(i=0;i<=tot;i++)
    	{
    		for(j=1;j<=alphabet;j++)
    		{
    			if(vis[trie[i][j]]==0)
    			{
    				a.ma[i+1][trie[i][j]+1]+=1.0/alphabet;
    			}
    			else
    			{
    				a.ma[i+1][1]+=1.0/alphabet;
    				a.ma[i+1][m]+=1.0/alphabet;
    			}
    		}
    	}
    	printf("%.12Lf\n",mul(f,qpow(a,b,m),n,m,k).ma[1][m]);
    	return 0;
    }
    

每日总结、反思

  • 调整好打模拟赛和不打模拟赛的时间。
  • 要学会猜结论。
    • 猜结论这条路任重而道远。

2.22

闲话

  • miaomiao 上午 7:5011:20 安排了一场模拟赛。
  • 中午家长说能去打省选,票买好了。

做题纪要

tgHZOJ 5897. 买汽水

tgHZOJ 5896. 最小生成树

tgHZOJ 5895. 舞会

tgHZOJ 5894. 打赌

POJ1321 棋盘问题

  • fi,j 表示到第 i 行,当前“摆放的棋子的状态”对应的二进制数为 j 时的方案数,状态转移方程为 {fi,j=fi1,jfi,j|(1k)=[visi,k=0]×[j&(1k)=0]×fi1,j

  • 最终,有 i=0(1n)1[popcount(i)=n]×fn,i 即为所求。

    点击查看代码
    int f[2][(1<<10)+1];
    char c[10][10];
    int lowbit(int x)
    {
    	return (x&(-x));
    }
    int popcount(int x)
    {
    	int ans=0;
    	for(int i=x;i>=1;i-=lowbit(i))
    	{
    		ans++;
    	}
    	return ans;
    }
    int main()
    {
    	int n,m,ans,i,j,k;
    	while(cin>>n>>m)
    	{
    		if(n==-1&&m==-1)
    		{
    			break;
    		}
    		else
    		{
    			memset(f,0,sizeof(f));
    			ans=0;
    			f[0][0]=1;
    			for(i=1;i<=n;i++)
    			{
    				for(j=1;j<=n;j++)
    				{
    					cin>>c[i][j-1];
    				}
    			}
    			for(i=1;i<=n;i++)
    			{
    				for(j=(1<<n)-1;j>=0;j--)
    				{
    					f[i&1][j]=f[(i-1)&1][j];
    					if(f[(i-1)&1][j]!=0)
    					{
    						for(k=0;k<=n-1;k++)
    						{
    							if(c[i][k]=='#'&&(j&(1<<k))==0)
    							{
    								f[i&1][j|(1<<k)]+=f[(i-1)&1][j];
    							}
    						}
    					}
    				}
    			}
    			for(i=0;i<=(1<<n)-1;i++)
    			{
    				ans+=(popcount(i)==m)*f[n&1][i];
    			}
    			cout<<ans<<endl;
    		}
    	}
    	return 0;
    }
    

POJ2411 Mondriaan's Dream

  • 观察到每一行仅与上一行的状态有关。若在上一行的分割状态存在一个长度为奇数的区间均被横着的 1×2 覆盖或上一行、下一行的分割状态同时存在一个位置被 1×2 的上半部分覆盖,则该状态不合法,无法向下一行转移。

  • fi,j 表示到第 i 行的“分割状态”对于的二进制数为 j 时,前 i 行可行的方案数,状态转移方程为 fi,j=j&k=0,j|kSfi1,k ,其中 S 中的数均满足二进制表示中每一段连续的 0 都必须有偶数个。

    点击查看代码
    ll f[15][(1<<15)+1],s[(1<<15)+1];
    int main()
    {
    	ll n,m,flag,sum,i,j,k;
    	while(cin>>n>>m)
    	{
    		if(n==0&&m==0)
    		{
    			break;
    		}
    		else
    		{
    			memset(f,0,sizeof(f));
    			f[0][0]=1;
    			for(i=0;i<=(1<<m)-1;i++)
    			{
    				flag=sum=0;
    				for(j=0;j<=m-1;j++)
    				{
    					if(((i>>j)&1)!=0)
    					{
    						if(sum%2==1)
    						{
    							flag=1;
    							break;
    						}
    						sum=0;
    					}
    					else
    					{
    						sum++;
    					}
    				}
    				s[i]=(flag==0&&sum%2==0);
    			}
    			for(i=1;i<=n;i++)
    			{
    				for(j=0;j<=(1<<m)-1;j++)
    				{
    					for(k=0;k<=(1<<m)-1;k++)
    					{
    						if((j&k)==0&&s[j|k]==1)
    						{
    							f[i][j]+=f[i-1][k];
    						}
    					}
    				}
    			}
    			cout<<f[n][0]<<endl;
    		}
    	}
    	return 0;
    }
    

luogu P1879 [USACO06NOV]玉米田Corn Fields

  • 处理出 visi 表示第 i 行能否种玉米的状态(用二进制数保存)。

  • fi,j 表示到第 i 行的“种玉米状态”对应的二进制数为 j 时,前 i 行可行的方案数,状态转移方程为 fi,j=j&k=0,kSfi1,k(visi&j=0,jS) ,其中 S 中的数均满足二进制表示中不存在两个连续的 1

    点击查看代码
    const ll p=100000000;
    ll f[15][(1<<15)+1],s[(1<<15)+1],vis[(1<<15)+1];
    int main()
    {
    	ll n,m,pd,ans=0,flag,sum,i,j,k;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			cin>>pd;
    			vis[i]+=((pd==0)<<(j-1));
    		}
    	}
    	f[0][0]=1;
    	for(i=0;i<=(1<<m)-1;i++)
    	{
    		flag=sum=0;
    		for(j=0;j<=m-1;j++)
    		{
    			if(((i>>j)&1)==0)
    			{
    				if(sum>=2)
    				{
    					flag=1;
    					break;
    				}
    				sum=0;
    			}
    			else
    			{
    				sum++;
    			}
    		}
    		s[i]=(flag==0&&sum<=1);
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=0;j<=(1<<m)-1;j++)
    		{
    			if(s[j]==1&&(vis[i]&j)==0)
    			{
    				for(k=0;k<=(1<<m)-1;k++)
    				{  
    					if((j&k)==0&&s[k]==1)
    					{
    						f[i][j]=(f[i][j]+f[i-1][k])%p;
    					}
    				}
    			}
    		}
    	}
    	for(i=0;i<=(1<<m)-1;i++)
    	{
    		ans=(ans+f[n][i])%p;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

2.23

闲话

  • 发现上午高一考的题我都不会,难崩。但教练组题的时候是以为要省选了,所以放几道省选难度的题不足为奇吗?是以为学到单调队列就能稳定切知识点已学过的蓝题了吗?
  • 中午睡觉的时候有小孩来我们宿舍闹,然后 @SoyTony 就来救场了。
  • 晚上吃饭前看见我初二生物老师了。

做题纪要

luogu P1896 [SCOI2005]互不侵犯

  • fi,j,k 表示第 i 行的“放置国王状态”对应的二进制数为 k 时,共放置了 j 个国王的方案数。状态转移方程为 fi,h,j=kS,j&k=0,(k1)&j=0,(k1)&j=0fi1,hpopcount(j),k(hpopcount(j),jS)

  • 转移过程中貌似会出现 long long 溢出的情况,但不知道为啥没对答案产生影响。

    点击查看代码
    ll f[12][102][(1<<12)+1],s[(1<<12)+1];
    int main()
    {
    	ll n,m,ans=0,flag,sum,i,j,k,h;
    	cin>>n>>m;
    	f[0][0][0]=1;
    	for(i=0;i<=(1<<n)-1;i++)
    	{
    		flag=sum=0;
    		for(j=0;j<=n-1;j++)
    		{
    			if(((i>>j)&1)==0)
    			{
    				if(sum>=2)
    				{
    					flag=1;
    					break;
    				}
    				sum=0;
    			}
    			else
    			{
    				sum++;
    			}
    		}
    		s[i]=(flag==0&&sum<=1);
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(h=0;h<=m;h++)
    		{
    			for(j=0;j<=(1<<n)-1;j++)
    			{
    				if(s[j]==1&&h>=__builtin_popcount(j))
    				{
    					for(k=0;k<=(1<<n)-1;k++)
    					{
    						if(s[k]==1&&(j&k)==0&&((k<<1)&j)==0&&((k>>1)&j)==0)       
    						{
    							f[i][h][j]+=f[i-1][h-__builtin_popcount(j)][k];
    						}
    					}
    				}
    			}
    		}
    	}
    	for(i=0;i<=(1<<n)-1;i++)
    	{
    		ans+=f[n][m][i];
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

AT_dp_o Matching

  • fi,j 表示到第 i 个男生的“已经选取女生的状态”对应的二进制数为 j 的方案数。状态转移方程为 fi,j=k=0n1[visi,k=1]×[j&(1k)0]×fi1,j(1k)(popcount(j)=i)

  • 需要滚动数组优化。

    点击查看代码
    const ll p=1000000007;
    ll f[2][(1<<25)+1],vis[25][25];
    int main()
    {
    	ll n,i,j,k;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			cin>>vis[i][j-1];
    		}
    	}
    	f[0][0]=1;
    	for(i=1;i<=n;i++)
    	{
    		for(j=0;j<=(1<<n)-1;j++)
    		{
    			if(__builtin_popcount(j)==i)
    			{
    				for(k=0;k<=n-1;k++)
    				{
    					if(vis[i][k]==1&&(j&(1<<k)))
    					{
    						f[i&1][j]=(f[i&1][j]+f[(i-1)&1][j-(1<<k)])%p;
    					}
    				}
    			}
    		}
    	}
    	cout<<f[n&1][(1<<n)-1]<<endl;
    	return 0;
    }
    

luogu P2915 [USACO08NOV]Mixed Up Cows G

  • fi,j 表示以第 i 头牛作为结尾的“选取奶牛状态”对应的二进制数为 j 的方案数。状态转移方程为 fk,i=i&(1j)0,jk,|ajak|>mfj,i(1k),(i&(1k)0)

  • 最终,有 i=0n1fi,(1n)1 即为所求。

    点击查看代码
    ll a[20],f[20][(1<<20)+1];
    int main()
    {
    	ll n,m,ans=0,i,j,k;
    	cin>>n>>m;
    	for(i=0;i<=n-1;i++)
    	{
    		cin>>a[i];
    		f[i][1<<i]=1;
    	}
    	sort(a+0,a+n);
    	for(i=0;i<=(1<<n)-1;i++)
    	{
    		for(j=0;j<=n-1;j++)
    		{
    			if(i&(1<<j))
    			{
    				for(k=0;k<=n-1;k++)
    				{
    					if((i&(1<<k))&&abs(a[j]-a[k])>m)
    					{
    						f[k][i]+=f[j][i-(1<<k)];
    					}
    				}
    			}
    		}
    	}
    	for(i=0;i<=n-1;i++)
    	{
    		ans+=f[i][(1<<n)-1];
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P2704 [NOI2001]炮兵阵地

  • 预处理出 visi 表示第 i 行能否不放炮兵的状态(用二进制数保存)。

  • 预处理出 S 中储存相邻两个 1 之间至少有 20 的数。

  • fi,j,k 表示第 i 行的“放置炮兵状态”对应的二进制数为 j ,第 i1 行的“放置炮兵状态”对应的二进制数为 k 时的放置炮兵总数的最大值。状态转移方程为 fi,j,k=maxj&h=0,k&h=0,h&visi2=0{fi1,k,h+popcount(j)}(j&k=0j&visi=0,k&visi1=0)

  • 需要使用离散化的思想,用 i 替代 Si 来节省空间。

    • S 中仅包含不到 100 个数。
  • 最终,有 maxi=0|S|1{maxj=0|S|1{fn,i,j}} 即为所求。

    点击查看代码
    ll f[200][200][200],vis[20];
    vector<ll>state;
    int main()
    {
    	ll n,m,ans=0,flag,i,j,k,h;
    	char pd;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			cin>>pd;
    			vis[i+1]+=((pd=='H')<<(j-1));
    		} 
    	}
    	for(i=0;i<=(1<<m)-1;i++)
    	{
    		flag=0;
    		for(j=0;j<=m-1;j++)
    		{
    			if((((i>>j)&1)&&((i>>(j+1))&1))||(((i>>j)&1)&&((i>>(j+2))&1)))//判断相邻两个1之间是否至少2个0
    			{
    				flag=1;
    				break;
    			}
    		}
    		if(flag==0)
    		{
    			state.push_back(i);
    		}
    	}
    	for(i=2;i<=n+1;i++)
    	{
    		for(j=0;j<state.size();j++)
    		{
    			if((state[j]&vis[i])==0)
    			{
    				for(k=0;k<state.size();k++)
    				{
    					if((state[j]&state[k])==0&&(state[k]&vis[i-1])==0)
    					{
    						for(h=0;h<state.size();h++)
    						{
    							if((state[j]&state[h])==0&&(state[k]&state[h])==0&&(state[h]&vis[i-2])==0)
    							{
    								f[i][j][k]=max(f[i][j][k],f[i-1][k][h]+__builtin_popcount(state[j]));
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    	for(i=0;i<state.size();i++)
    	{
    		for(j=0;j<state.size();j++)
    		{
    			ans=max(ans,f[n+1][i][j]);
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P5911 [POI2004]PRZ

  • 多倍经验: luogu P3052 [USACO12MAR] Cows in a Skyscraper G | AT_dp_u Grouping

  • 预处理出“过桥状态”对于的二进制数为 i 时,花费的时间 ti 和总重量 sumwi

  • fi 表示“过桥状态”对于的二进制数为 i 时的需要最小时间。状态转移方程为 fi=minji,sumwjm{fij+tj}

    • 现在考虑如何枚举 j
      for(i=0;i<=(1<<n)-1;i++)
      	for(j=i;j!=0;j=i&(j-1))
      
    • 上述代码中每次 j-1 后都会把当前状态的最后一个 1 后面的所有位都变为 1 ,这一位变成 0 ,再 &i 后将不能为 1 的每一位变成 0 ,这就保证了当前枚举的一定是子集,故当前就可以枚举到比上一个状态小的所有状态中最大的状态。
    • 时间复杂度为 O(i=0n(ni)2i=i=0n(ni)2i1ni=(1+2)n=3n)
    点击查看代码
    int tt[20],ww[20],t[1<<20],w[1<<20],f[1<<20];
    int main()
    {
    	int n,m,i,j;
    	cin>>m>>n;
    	memset(f,0x3f,sizeof(f));
    	f[0]=0;
    	for(i=0;i<=n-1;i++)
    	{
    		cin>>tt[i]>>ww[i];
    	}
    	for(i=0;i<=(1<<n)-1;i++)
    	{
    		for(j=0;j<=n-1;j++)
    		{
    			if((i>>j)&1)
    			{
    				t[i]=max(t[i],tt[j]);
    				w[i]+=ww[j];
    			}
    		}
    	}
    	for(i=0;i<=(1<<n)-1;i++)
    	{   
    		for(j=i;j!=0;j=i&(j-1))
    		{
    			if(w[j]<=m)
    			{
    				f[i]=min(f[i],f[i-j]+t[j]);
    			}
    		}
    	}
    	cout<<f[(1<<n)-1]<<endl;
    	return 0;
    }
    

BZOJ1688 Disease Manangement 疾病管理

  • 预处理出 visi 表示第 i 头牛的疾病含有状态。

  • fi 表示“疾病含有状态”对应的二进制数为 i 时能够选择的最多奶牛。状态转移方程为 fj|visi=max{fj+1}

    点击查看代码
    int vis[(1<<20)],f[(1<<20)];
    int main()
    {
    	int n,d,k,num,ans=0,x,i,j;
    	cin>>n>>d>>k;
    	for(i=1;i<=n;i++)
    	{
    		cin>>num;
    		for(j=1;j<=num;j++)
    		{
    			cin>>x;
    			vis[i]+=(1<<(x-1));
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=(1<<d)-1;j>=0;j--)
    		{
    			f[j|vis[i]]=max(f[j|vis[i]],f[j]+1);
    		}
    	}
    	for(i=0;i<=(1<<d)-1;i++)
    	{
    		if(__builtin_popcount(i)<=k)
    		{
    			ans=max(ans,f[i]);
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P5764 [CQOI2005]新年好

  • 预处理出 1,a,b,c,d,e 到所有点的最短路长度。

  • a,b,c,d,e 进行全排列,枚举所有可能的经过顺序。

    点击查看代码
    struct node
    {
    	ll nxt,to,w;
    }e[400000];
    ll head[400000],dis[10][400000],vis[400000],p[10],id[400000],cnt=0;
    void add(ll u,ll v,ll w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    void dijkstra(ll s)
    {
    	memset(dis[id[s]],0x3f,sizeof(dis[id[s]]));
    	memset(vis,0,sizeof(vis));
    	priority_queue<pair<ll,ll> >q;
    	ll x,i;
    	dis[id[s]][s]=0;
    	q.push(make_pair(0,-s));
    	while(q.empty()==0)
    	{
    		x=-q.top().second;
    		q.pop();
    		if(vis[x]==0)
    		{
    			vis[x]=1;
    			for(i=head[x];i!=0;i=e[i].nxt)
    			{
    				if(dis[id[s]][e[i].to]>dis[id[s]][x]+e[i].w)
    				{
    					dis[id[s]][e[i].to]=dis[id[s]][x]+e[i].w;
    					q.push(make_pair(-dis[id[s]][e[i].to],-e[i].to));
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	ll n,m,u,v,w,sum,ans=0x7f7f7f7f,i;
    	cin>>n>>m;
    	p[1]=1;
    	id[1]=1;
    	for(i=2;i<=6;i++)
    	{
    		cin>>p[i];
    		id[p[i]]=i;
    	}
    	sort(p+1,p+1+6);
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v>>w;
    		add(u,v,w);
    		add(v,u,w);
    	}
    	for(i=1;i<=6;i++)
    	{
    		dijkstra(p[i]);
    	}
    	do
    	{
    		sum=0;
    		for(i=1;i<=5;i++)
    		{
    			sum+=dis[id[p[i]]][p[i+1]];
    		}
    		ans=min(ans,sum);
    	}while(next_permutation(p+2,p+1+6));
    	cout<<ans<<endl;
    	return 0;
    }
    

每日总结、反思

  • 菜就多练。

2.24

闲话

  • 早上不知道为啥 huge 让提前两分钟去吃饭。
  • 上午放假了。

做题纪要

posted @   hzoi_Shadow  阅读(113)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
扩大
缩小
点击右上角即可分享
微信分享提示