1. 程式人生 > 其它 >8.19考試總結(NOIP模擬44)[Emotional Flutter·Medium Counting·Huge Counting·字元消除2 ]

8.19考試總結(NOIP模擬44)[Emotional Flutter·Medium Counting·Huge Counting·字元消除2 ]

前言

幾乎是大暑假的最後一次考試了。

我也迎來了我的第一次報零(霧

T1 Emotional Flutter

解題思路

比較考驗思維能力,其實就是區間覆蓋問題。

我考場上是想到了一個 dfs 實現區間覆蓋的做法(手動實現線段樹???)。

查詢最後剩下的沒有被黑條覆蓋的長度是否大於腳長。

只可惜最後寫了半天的程式碼手動模擬了十餘組樣例,最後時空雙爆(裂開。

正解思路有一些變化,把每一個黑條的長度增加腳長的長度,白條減去。

然後直接查詢是否有空隙就好了,把每一個黑條的左右邊界 \(\bmod\) 一下步長。

如果取 \(\bmod\) 之後 \(R>L\) 的話就把這個黑條劈成兩半,放在兩端。

程式碼實現需要卡一下邊界

code

50ptsdfs

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e5+10;
int T,n,m,len,s[N],pre[N];
bool flag;
void dfs(int L,int R)
{
	if(flag||R<L||R-L+1<len)	return ;
	if(L-m<=0||R-m<=0)
	{
		if(L-m<=0&&R-m<=0&&R-L+1>=len)	flag=true;
		if(L-m<=0&&R-m>0&&0-L+m+1>=len)	flag=true;	
		if(R-m<=0)	return ;
	}
	int p1=upper_bound(pre+1,pre+n+1,L-m)-pre-1;
	int p2=lower_bound(pre+1,pre+n+1,R-m)-pre;
	if(p1>p2)	return ;
	if(!p1)
	{
		dfs(L,0);
		L=max(L,pre[1]+1);
		p1++;
	}
	for(int i=p1;i<=p2;i++)
		if(!(i&1))
			dfs(max(L-m,pre[i-1]+1),min(R-m,pre[i]));
}
void solve()
{
	memset(pre,0,sizeof(pre));
	len=read();	m=read();	n=read();
	flag=false;
	for(int i=1;i<=n;i++)
		s[i]=read();
	for(int i=1;i<=n;i++)
		if(s[i]>=m&&(i&1)){printf("NIE\n");return;}
	for(int i=1;i<=n;i++)
		pre[i]=pre[i-1]+(s[i]%m==0?m:s[i]%m);
	dfs(pre[n]+1,pre[n]+m);
	if(flag)	printf("TAK\n");
	else	printf("NIE\n");
}
signed main()
{
	T=read();
	while(T--)	solve();
	return 0;
}

正解

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e5+10;
int T,len,tot,cnt,m,n,pre,s[N];
bool flag;
struct Node
{
	int l,r;
	bool friend operator < (Node x,Node y)
	{
		if(x.l==y.l)	return x.r>y.r;
		return x.l<y.l;
	}
}p[N<<1];
void add(int &x,int y,int mod){x+=y;if(x>=mod)x-=mod;}
void solve()
{
	len=read();	m=read();	n=read();
	pre=tot=cnt=0;	flag=false;
	for(int i=1;i<=n;i++)
		s[i]=read();
	for(int i=1;i<=n;i++)
		if(s[i]+len>m&&(i&1)){printf("NIE\n");return;}
	if(len>=m){printf("NIF");return;}
	for(int i=1;i<=n;i++)
	{
		if(i&1)
		{
			s[i]+=len;	s[i+1]-=len;
			int L=(pre+1)%m,R=(pre+s[i]-1)%m;
			if(L<=R){p[++tot]=(Node){L,R};goto V;}
			p[++tot]=(Node){L,m-1};
			p[++tot]=(Node){0,R};
		}
		V:;	add(pre,s[i],m);
	}
	sort(p+1,p+tot+1);	p[++cnt]=p[1];
	for(int i=2;i<=tot;i++)
		if(p[i].l<p[cnt].l||p[i].r>p[cnt].r)
			p[++cnt]=p[i];
	if(p[1].l>0||p[cnt].r<m-1)	flag=true;
	for(int i=1;i<cnt&&!flag;i++)
		flag|=(p[i].r+1<p[i+1].l);
	if(flag)	printf("TAK\n");
	else	printf("NIE\n");
}
signed main()
{
	T=read();
	while(T--)	solve();
	return 0;
}

T2 Medium Counting

解題思路

又是 數位 DP

\(f_{l,r,pos,ch}\)表示考慮 \([l,r]\) 的字串的 pos 之前的位置至少填 ch 的方案數

轉移分為兩種:

  1. 考慮由更大的 ch 轉移 \(f_{l,r,pos,ch+1}\)

  2. 列舉分界點 \(mid\in[l,r]\)\(S_l \sim S_{mid}\) 的 pos 位都是 ch,
    可以由\(f_{l,mid,pos,0}\times f_{mid+1,r,pos+1,ch}\)轉移

剩下的就是用dfs實現數位 DP 了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=60,M=30,mod=990804011;
int n,Len,s[N][M],f[N][N][M][M];
char ch[M];
void add(int &x,int y){x+=y;if(x>mod)x-=mod;}
int dfs(int l,int r,int pos,int cha)
{
	if(~f[l][r][pos][cha])	return f[l][r][pos][cha];
	if(l>r)	return f[l][r][pos][cha]=1;
	if(pos>Len)	return f[l][r][pos][cha]=(l==r);
	if(cha>26)	return f[l][r][pos][cha]=0;
	f[l][r][pos][cha]=dfs(l,r,pos,cha+1);
	for(int mid=l;mid<=r&&(s[mid][pos]==cha||(s[mid][pos]==-1&&cha));mid++)
		add(f[l][r][pos][cha],dfs(l,mid,pos+1,0)*dfs(mid+1,r,pos,cha+1)%mod);
	return f[l][r][pos][cha];
}
signed main()
{
	memset(f,-1,sizeof(f));
	n=read();
	for(int i=1,len;i<=n;i++)
	{
		scanf("%s",ch+1);
		len=strlen(ch+1);
		Len=max(len,Len);
		for(int j=1;j<=len;j++)
			s[i][j]=(ch[j]=='?'?-1:ch[j]-'a'+1);
	}
	printf("%lld",dfs(1,n,1,0));
	return 0;
}

T3 Huge Counting

解題思路

可重集排列問題。

組合數求方案數,由二維可以擴充套件到 k 維 \(\dfrac{(\sum x_i)!}{\prod (x_i-1)!}\)

然後直接大力 容斥+判斷分數上下的 2 的數,奇加偶減。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=10,Lg=50,mod=990804011;
int T,n,f[Lg+10][1<<N],l[N],r[N],lim[N];
void add(int &x,int y){x+=y;if(x>mod)x-=mod;else if(x<0)x+=mod;}
int work()
{
	int ans=0;
	for(int i=1;i<=n;i++)
		if(lim[i]==-1)
			return 0;
	memset(f,0,sizeof(f));
	f[Lg][(1<<n)-1]=1;
	for(int i=Lg;i>=1;i--)
	{
		int st=0;
		for(int j=1;j<=n;j++)
			if((lim[j]>>i-1)&1)
				st|=1<<j-1;
		for(int sta=0;sta<(1<<n);sta++)
			if(f[i][sta])
			{
				int com=sta&(st^((1<<n)-1));
				add(f[i-1][com],f[i][sta]);
				for(int j=1;j<=n;j++)
					if(((sta>>j-1)&1)^1)	add(f[i-1][com],f[i][sta]);
					else	if((st>>j-1)&1)	add(f[i-1][com|(1<<j-1)],f[i][sta]);
			}
	}
	for(int sta=0;sta<(1<<n);sta++)
		add(ans,f[0][sta]);
	return ans;
}
int solve()
{
	n=read();
	int	ans=0;
	for(int i=1;i<=n;i++)
		l[i]=read()-1,r[i]=read()-1;
	for(int sta=0,sym;sta<(1<<n);sta++)
	{
		sym=1;
		for(int i=1;i<=n;i++)
			if((sta>>i-1)&1)	lim[i]=r[i];
			else	lim[i]=l[i]-1,sym*=-1;
		add(ans,sym*work());
	}
	return (ans+mod)%mod;
}
signed main()
{
	T=read();
	while(T--)	printf("%lld\n",solve());
	return 0;
}

T4 字元消除2

解題思路

題目大意就是對於給出的字串求出一個字典序最小的 01 串與該字串的迴圈節相同。

Hash 預處理出前後綴相同的位置,計入一個數組 sta。

對於整個 sta 而言 \(sta_i\)\(sta_{i-1}\) 的關係分為兩種

  1. \(sta_i\le sta_{i-1}\times 2\) 那麼此時的 \(sta_i\) 這一段一定是有 \(sta_{i-1}\) 這個字串的字尾和字首的,因為前面已經是最優解,直接把前面的複製過來就好了。

  2. \(sta_i>sta_{i-1}\times 2\) 此時一定除了相同的前後綴之外中間還有一段區間,因為要保證字典序最小,一定是填 0 最優。
    但是這樣可能會導致迴圈節過多,因此我們需要在某個位置填一個 1 ,但是暴力維護顯然是不行的。
    用 KMP 優化,每次向前面跳 next,然後判斷當前的一段的長度是否是空出來區間長度的整數倍,如果是並且最後一位不是 1 ,那麼就是一個多出來的迴圈節,需要把最後一位改成 1 。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=2e5+10;
const ull base=1331ull;
int T,n,top,pos,sta[N],nxt[N];
ull has[N],p[N];
bool vis[N];
char ch[N];
void KMP(int l,int r)
{
	for(int i=l;i<=r;i++)
	{
		while(pos>0&&vis[i]!=vis[pos+1])	pos=nxt[pos];
		if(vis[i]==vis[pos+1])	pos++;
		nxt[i]=pos;
	}
}
void solve()
{
	memset(nxt,0,sizeof(nxt));
	memset(vis,false,sizeof(vis));
	top=pos=0;
	scanf("%s",ch+1);
	n=strlen(ch+1);
	for(int i=1;i<=n;i++)
		has[i]=has[i-1]*base+ch[i];
	for(int i=1;i<=n;i++)
		if(has[i]==has[n]-has[n-i]*p[i])
			sta[++top]=i;
	if(sta[1]>1)	vis[sta[1]]=true;
	KMP(2,sta[1]);
	for(int i=2;i<=top;i++)
		if(sta[i]<=sta[i-1]*2)
		{
			for(int j=sta[i-1]+1;j<=sta[i];j++)
				vis[j]=vis[j-sta[i]+sta[i-1]];
			KMP(sta[i-1]+1,sta[i]);
		}
		else
		{
			KMP(sta[i-1]+1,sta[i]-sta[i-1]-1);
			int x=pos,len=sta[i]-sta[i-1];
			while(x)
			{
				if(!vis[x+1]&&!(len%(len-x-1)))
				{
					vis[len]=true;
					break;
				}
				x=nxt[x];
			}
			if(!vis[x+1]&&!(len%(len-x-1)))
				vis[len]=true;
			while(pos>0&&ch[len]!=ch[pos+1])	pos=nxt[pos];
			if(ch[len]==ch[pos+1])	pos++;
			nxt[len]=pos;
			for(int j=1;j<=sta[i-1];j++)
				vis[j+len]=vis[j];
			KMP(len+1,sta[i]);
		}
	for(int i=1;i<=n;i++)
		printf("%d",vis[i]);
	printf("\n");
}
signed main()
{
	p[0]=1;for(int i=1;i<N;i++)p[i]=p[i-1]*base;
	T=read();
	while(T--)	solve();
	return 0;
}