noip模擬44[我想我以後會碰見計數題就溜走的]

fengwu2005發表於2021-08-22

noip模擬44 solutions

這一場抱零的也忒多了,我也只有45pts

據說好像是把幾套題裡面最難的收拾出來讓我們考得

好慘烈啊,這次的考試我只有第一題騙了40pts,其他都抱零了

T1 Emotional Flutter

這個我直接用線段樹維護解集,

這樣的,你不能踩到每一個黑塊,所以每一個黑塊都對應著一個不踩他的範圍

我們用線段樹維護這些範圍的交集,因為值域有點大所以炸了

40pts


#include<bits/stdc++.h>
using namespace std;
#define re register int 
#define ll long long
const int N=5e5+5;
ll T,s,k,n,cnt,a[N],pre[N];
struct XDS{
	int mx[N*15],ls[N*15],rs[N*15],laz[N*15];
	int seg,rt;
	void clear(){seg=0;rt=0;mx[0]=mx[1]=0;}
	int newnode(){++seg;ls[seg]=rs[seg]=mx[seg]=laz[seg]=0;return seg;}
	void pushup(int x){
		mx[x]=max(mx[ls[x]],mx[rs[x]]);
		return ;
	}
	void pushdown(int x){
		if(!laz[x])return ;
		if(!ls[x])ls[x]=newnode();
		if(!rs[x])rs[x]=newnode();
		mx[ls[x]]+=laz[x];
		mx[rs[x]]+=laz[x];
		laz[ls[x]]+=laz[x];
		laz[rs[x]]+=laz[x];
		laz[x]=0;
		return ;
	}
	void ins(int &x,int l,int r,int ql,int qr){
		if(ql>r||qr<l||ql>qr)return ;
		if(!x)x=newnode();
		if(ql<=l&&r<=qr){
			laz[x]++;mx[x]++;
			return ;
		}
		pushdown(x);
		int mid=l+r>>1;
		if(ql<=mid)ins(ls[x],l,mid,ql,qr);
		if(qr>mid)ins(rs[x],mid+1,r,ql,qr);
		pushup(x);return ;
	}
}xds;
signed main(){
	//cout<<(sizeof(xds.mx)*4+sizeof(a)+sizeof(pre)>>20)<<endl;
	//xds.mx[1]=10000;cout<<xds.mx[1]<<endl;
	scanf("%lld",&T);
	while(T--){cnt=0;
		scanf("%lld%lld%lld",&s,&k,&n);
		for(re i=1;i<=n;i++){
			scanf("%lld",&a[i]),pre[i]=pre[i-1]+a[i];
		}
		for(re i=0;i<=n;i++){
			if(i&1)continue;cnt++;
			ll tmp=pre[i]%k,l0=-1,r0=-1,l1=-1,r1=-1;
			if(a[i+1]+tmp+s<=k){
				l0=0,r0=k-a[i+1]-tmp-s;
				xds.ins(xds.rt,0,k-1,l0,r0);
			}
			if(tmp){
				l1=k-tmp,r1=min(l1+k-a[i+1]-s,k-1);
				xds.ins(xds.rt,0,k-1,l1,r1);
			}
		}
		if(xds.mx[xds.rt]>=cnt)printf("TAK\n");
		else printf("NIE\n");
		xds.clear();
	}
}

其實正解也是有\(log\)的,只不過他的放在了1e5上,而我的放在了1e9*1e5上,並且空間好像也不太夠

每次只能跨越k個格,那麼我們每次踩到的點在%k意義下都在一個點上

並且這個點不能被任何一個黑塊覆蓋,所以把所有黑塊弄下來

排序,減去沒用的,然後去覆蓋,如果能找到空缺就是合法的

腳長為s,直接對黑塊的長的+s,

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=5e5+5;
ll T,s,k,n,a[N],fro;
struct node{
	ll l,r;
	node(){}
	bool operator < (node x)const{
		if(l==x.l)return r>x.r;
		return l<x.l;
	}
}sca[N];
int cnt,mnt;
signed main(){
	scanf("%lld",&T);
	while(T--){
		cnt=0;mnt=0;fro=0;
		scanf("%lld%lld%lld",&s,&k,&n);
		//if(k<s){printf("NIE\n");continue;}
		bool flag=false;
		for(re i=1;i<=n;i++){
			scanf("%lld",&a[i]);
			if(a[i]+s>k&&(i&1))flag=true;
		}
		if(flag||k<s){printf("NIE\n");continue;}
		//cout<<"Sb"<<endl;
		for(re i=1;i<=n;i++){
			if((i^1)&1){fro+=a[i];continue;}
			a[i]+=s;a[i+1]-=s;
			ll pl=(fro+1)%k,pr=(fro+a[i]-1)%k;
			//cout<<i<<" "<<pl<<" "<<pr<<endl;
			fro+=a[i];
			sca[++cnt].l=pl;
			if(pr>=pl)sca[cnt].r=pr;
			else {
				sca[cnt].r=k-1;
				sca[++cnt].l=0;
				sca[cnt].r=pr;
			}
			//cout<<i<<" "<<sca[cnt].l<<" "<<sca[cnt].r<<endl;
		}
		sort(sca+1,sca+cnt+1);mnt=1;
		for(re i=2;i<=cnt;i++){
			if(sca[i].l>=sca[mnt].l&&sca[i].r<=sca[mnt].r)continue;
			sca[++mnt]=sca[i];
		}
		flag=false;
		if(sca[1].l!=0||sca[mnt].r!=k-1)flag=true;
		//cout<<flag<<endl;
		for(re i=1;i<mnt;i++){
			if(sca[i].r+1<sca[i+1].l){
				//cout<<i<<endl;
				flag=true;
			}
			if(flag)break;
		}
		//cout<<sca[1].l<<" "<<sca[mnt].r<<endl;
		if(flag)printf("TAK\n");
		else printf("NIE\n");
	}
}

注意邊界問題極其ex

T2 Medium Counting

正解和我考場上想的一樣,就是利用後面的大小方案推出前面的方案數

但是我考場上只有一個初步的思路,沒有具體的實現出來,所以00000

如果當前字串短的話,後面補0就行了

然後我們對於當前這一位,我們列舉分界點,看啥時候+1

然後直接記憶化搜尋就好啦

dp陣列含義:dp[i][j][l][r]表示i位,最小的是c,範圍是l-r,的方案數

如果當前這一位被認定為c,那麼前面的字串就不能根據當前這一位來判斷

只能用後面的字元來區別他們的大小,這個時候就轉移到了下一層

最後小小的判一下邊界就行了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const ll mod=990804011;
ll n,dp[25][28][55][55];
char ch[55][25];
int a[55][25];
ll dfs(int p,int c,int l,int r){
	if(dp[p][c][l][r]!=-1)return dp[p][c][l][r];
	if(l>r)return dp[p][c][l][r]=1;
	if(p>20)return dp[p][c][l][r]=(l==r);
	if(c>26)return 0;
	dp[p][c][l][r]=dfs(p,c+1,l,r);
	//cout<<dp[p][c][l][r]<<endl;
	for(re i=l;i<=r;i++){
		if((a[i][p]!=c&&a[i][p]!=27)||(a[i][p]==27&&!c))break;
		dp[p][c][l][r]=(dp[p][c][l][r]+dfs(p+1,0,l,i)*dfs(p,c+1,i+1,r)%mod)%mod;
	}
	return dp[p][c][l][r];
}
signed main(){
	memset(dp,-1,sizeof(dp));
	scanf("%lld",&n);
	for(re i=1;i<=n;i++){
		scanf("%s",ch[i]+1);
		for(re j=1;j<=strlen(ch[i]+1);j++){
			if(ch[i][j]=='?')a[i][j]=27;
			else a[i][j]=ch[i][j]-'a'+1;
		}
	}
	printf("%lld",dfs(1,0,1,n));
}

T3 Huge Counting

這個又是一個更加噁心的計數題,我真的不想說啥了

還是數位dp,害,氣死人了

他們都說挺顯然的是可重集排列,呵呵呵,我TM看了半天才看出來

每次選擇一個數-1,所以減的順序就是一個排列,因為好多同一位的減法是相同的,所以可重

那就直接套公式就行了\(\huge\frac{(\sum{x_i})!}{\prod{x_i!}}\)

於是你只需要判斷奇偶性就行了(然而我考場上並沒有看見mod2)

用那個經典公式\(sum_2(x!)=\frac{x}{2}+\frac{x}{4}+\frac{x}{8}+.....\)

暴力求的話可以拿到40pts的好成績

40pts
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=15;
const ll mod=990804011;
ll T,k,l[N],r[N],ans,ji[N];
void dfs(int x){
	if(x==k+1){
		ll tmp=0,sum=0,t=2;
		for(re i=1;i<x;i++)tmp+=ji[i];
		tmp/=2;while(tmp)sum+=tmp,tmp/=2;
		for(re i=1;i<x;i++){
			ll now=ji[i]/2;
			while(now)sum-=now,now/=2;
		}
		if(!sum)ans++;
		//if(ji[1]!=0)cout<<ji[1]<<endl;
		if(ans>mod)ans-=mod;
		return ;
	}
	for(ll i=l[x];i<=r[x];i++){
		ji[x]=i-1;dfs(x+1);
	}
}
signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&k);ans=0;
		for(re i=1;i<=k;i++)scanf("%lld%lld",&l[i],&r[i]);
		dfs(1);
		printf("%lld\n",ans);
	}
}

然後你發現上下兩部分,如果加和沒有進位的話,他們兩個有的2的因子個數是相同的

所以如果想讓這些x對答案有貢獻,就要讓這些x的二進位制表示每一位上至多有1個1

所以直接數位do,設dp[i][s]表示第i位,卡邊界的情況為s,就是每一個x的

注意不要妄想去卡上下邊界,因為陣列開不出來,所以要容斥,多步的

我是用dfs實現的容斥係數,也可以用1的個數的奇偶性來確定

直接轉移就行了,我是看完題解程式碼才會的。。。。。。。

轉移的時候,我們要先把每一個卡邊界的0拿出來,

這時候全是0,方案可行,

再去列舉每一個可能的1,如果卡邊界就要把邊界卡上,

如果不卡,那就還是原來的邊界

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=15;
const ll mod=990804011;
ll T,k,l[N],r[N],ans,ji[N];
ll f[52][1<<10];
ll get_ans(){
	memset(f,0,sizeof(f));
	f[50][(1<<k)-1]=1;
	for(re i=50;i>=1;i--){
		for(re j=0;j<(1<<k);j++){
			if(!f[i][j])continue;
			ll tmp=0;
			for(re l=1;l<=k;l++)
				if((j>>l-1)&1 && ((ji[l]>>i-1)^1)&1)
					tmp|=(1<<l-1);
			f[i-1][tmp]=(f[i-1][tmp]+f[i][j])%mod;
			for(re l=1;l<=k;l++)
				if(((j>>l-1)^1)&1)f[i-1][tmp]=(f[i-1][tmp]+f[i][j])%mod;
				else if((ji[l]>>i-1)&1)f[i-1][tmp|(1<<l-1)]=(f[i-1][tmp|(1<<l-1)]+f[i][j])%mod;
		}
	}
	ll ret=0;
	for(re i=0;i<(1<<k);i++)ret=(ret+f[0][i])%mod;
	return ret;
}
void dfs(int x,int fl){
	if(x==k+1){
		ans=(ans+get_ans()*fl+mod)%mod;
		return ;
	}
	ji[x]=r[x];
	dfs(x+1,fl);
	if(l[x]-1>=0){
		ji[x]=l[x]-1;
		dfs(x+1,-fl);
	}
}
signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&k);ans=0;
		for(re i=1;i<=k;i++)scanf("%lld%lld",&l[i],&r[i]),l[i]--,r[i]--;
		dfs(1,1);
		printf("%lld\n",ans);
	}
}

T4 字元消除2

說真的,一開始我連題面都沒看懂,

後來發現是找到一個和原字串t集合相同的01串

這樣的話,我們先找到所有可行的t

直接用KMP從n向前跳,所有跳出來的數,用n減去它,就是一個可行的t

然後考慮構造這個01串,我們要把KMP匹配到的所有的長度記錄下來

如果下一個的長度\(\le\)當前的2倍,那就直接複製過去就行了

不然的話,我就把中間的全乾成0,然後再跑一邊KMP,看看還是不是原來的匹配

我是直接將之前的p指標記錄下來,然後直接跳回去,

如果不是原來的匹配了,就把最後一位換成1

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=2e5+5;
char ch[N];
int T,n,fail[N],p,len[N],cnt;
int nxt[N],pri[N],tot;
void kmp(int l,int r){
	for(re i=l;i<=r;i++){
		while(pri[i]!=pri[p+1]&&p)p=nxt[p];
		if(pri[i]==pri[p+1])p++;
		nxt[i]=p;
	}
}
signed main(){
	scanf("%d",&T);
	while(T--){
		memset(fail,0,sizeof(fail));
		memset(len,0,sizeof(len));
		memset(nxt,0,sizeof(nxt));
		memset(pri,0,sizeof(pri));
		p=cnt=tot=0;
		scanf("%s",ch+1);n=strlen(ch+1);
		//cout<<"sb"<<endl;
		for(re i=2,j=0;i<=n;i++){
			while(ch[i]!=ch[j+1]&&j)j=fail[j];
			if(ch[i]==ch[j+1])j++;
			//cout<<i<<" "<<j<<endl;
			fail[i]=j;
		}
		int now=n;while(now)len[++cnt]=now,now=fail[now];//cout<<now<<endl;
		if(len[cnt]>1)pri[len[cnt]]=1;
		kmp(2,len[cnt]);
		//cout<<"sb"<<endl;
		//cout<<p<<endl;
		for(re i=cnt-1;i>=1;i--){
			if(len[i]>len[i+1]*2){
				kmp(len[i+1]+1,len[i]-len[i+1]-1);
				int pi=p;
				for(re j=1;j<=len[i+1];j++)pri[len[i]-len[i+1]+j]=pri[j];
				kmp(len[i]-len[i+1],len[i]);
				//for(re j=1;j<=len[i];j++)cout<<pri[j];cout<<endl;
				//cout<<nxt[len[i]]<<endl;
				if(nxt[len[i]]!=len[i+1]){
					p=pi;pri[len[i]-len[i+1]]=1;
					kmp(len[i]-len[i+1],len[i]);
				}
			}
			else {
				for(re j=len[i+1]+1;j<=len[i];j++)
					pri[j]=pri[j-len[i]+len[i+1]];
				kmp(len[i+1]+1,len[i]);
			}
		}
		for(re i=1;i<=n;i++)printf("%d",pri[i]);
		printf("\n");
	}
}