1. 程式人生 > 其它 >Noip模擬85 2021.10.29(致鬱四部曲)

Noip模擬85 2021.10.29(致鬱四部曲)

T1 莓良心 T2 盡梨了 T3 團不過 T4 七負我

前言

感謝出題人,我考場上直接被致鬱

雖然就只看過《Darling in the FRANXX》,還是為了\(ichigo\)難受了半天

T1 莓良心\((ichigo)\)

我打扮成你喜歡的樣子來看你了,廣,不,da,darling...

難受啊!!!可是決定切\(T1\)的我沒能切掉也很難受

考場上一看就看過一部番,剩下的就不管了,就打\(T1\)

然而還是隻能說容斥這個東西過於強大了,考場上打表+\(dp\)推了半天也沒推出來的斯特林數第\(n\)行,容斥兩三下就搞定了

\(\begin{Bmatrix}n\\k\end{Bmatrix}=\frac{1}{k!}\sum\limits_{i=0}^{k}\binom{k}{i}(k-i)^{n}\)

解釋一下就是列舉\(k\)個集合裡面有幾個空集,那麼選出這些空集後剩下的數隨便放在\((k-i)\)個非空集合中,然後乘上容斥係數,然後再亂排\(k!\)

那麼這道題就很簡單了,發現每一種岩漿能源做貢獻的次數相等,那麼只考慮第一個岩漿能源做貢獻的次數即可

不難得出是\(\begin{Bmatrix}n\\k\end{Bmatrix}+(n-1)\times\begin{Bmatrix}n-1\\k\end{Bmatrix}\)

所以答案就是\(ans=\sum w\times(\begin{Bmatrix}n\\k\end{Bmatrix}+(n-1)\times\begin{Bmatrix}n-1\\k\end{Bmatrix})\)

ichigo
#include<bits/stdc++.h>
#define int long long
#define si(i,x) for(int i=head[x],y=e[i].to;i;i=e[i].next,y=e[i].to)
using namespace std;
const int NN=1e6+5,mod=998244353;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
	};
}using namespace AE86;
int n,k,sm,pw[NN][2],prime[NN],len,h[NN],v[NN];
bool vis[NN];
auto qmo=[](int a,int b,int ans=1){
	int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
	return ans;
};
auto inv=[](int x){return qmo(x,mod-2);};
auto prework=[](){
	pw[1][0]=pw[1][1]=1; h[0]=h[1]=1;
	for(int i=2;i<=k;i++){
		h[i]=h[i-1]*i%mod;
		if(!vis[i]) prime[++len]=i,pw[i][0]=qmo(i,n),pw[i][1]=qmo(i,n-1);
		for(int j=1;j<=len&&prime[j]*i<=k;j++){
			vis[i*prime[j]]=1;
			pw[i*prime[j]][1]=pw[i][1]*pw[prime[j]][1]%mod;
			pw[i*prime[j]][0]=pw[i][0]*pw[prime[j]][0]%mod;
			if(i%prime[j]==0){
				break;
			}
		}
	}
	v[k]=inv(h[k]); v[0]=v[1]=1;
	for(int i=k-1;i>=2;i--) v[i]=v[i+1]*(i+1)%mod;
};
auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};
auto S=[](int opt,int k){
	int ans=qmo(h[k],mod-2),tmp=0;
	for(int i=0,bs=1;i<=k;i++,bs*=-1)
		tmp=(tmp+bs*C(k,i)*pw[k-i][opt]%mod+mod)%mod;
	return ans*tmp%mod;
};
namespace WSN{
	inline short main(){
		freopen("ichigo.in","r",stdin);
		freopen("ichigo.out","w",stdout);
		n=read(); k=read(); prework();
		for(int i=1;i<=n;i++) sm=(sm+read())%mod;
		int tmp=sm*((S(0,k)+(n-1)*S(1,k)%mod)%mod)%mod;
		write(tmp);
		return 0;
	}
}
signed main(){return WSN::main();}

T2 盡梨了\((eriri)\)

我成為你心中的第一位了嗎?

考場上碼出來了所有的\(dp\),唯獨就沒有給陣列排序

因為覺得有\(dp\)了貪心沒罵意義,於是就假死了。。。

於是就發現了並換成了\(permutation\)。。。。

考慮如果在時刻\(t\)在商店\(i\)購買物品,結束後立即去商店\(j\)購買物品

那麼\(j\)會因為在\(i\)處等候而額外花費\((a_i\times t+b_i+1)\times a_j\)的時間

如果我們將二者交換順序,在時刻\(t\)\(j\)購買,

結束後立即去\(i\)購買,\(i\)會額外花費\((a_j\times t+b_j+1)\times a_i\)的時間

若先去\(i\)比先去\(j\)更優,就需要滿足

\((a_i\times t+b_i+1)\times a_j\leq(a_j\times t+b_j+1)\times a_i\)

\((b_i+1)\times a_j\leq(b_j+1)\times a_i\)

然後按照這個去排序,發現\(a_i=0\)的會在最後,那麼找到他們按照\(b\)升序排序

先把前面的\(a_i>0\)的按照\(dp\)做一遍

\(dp_{i,j}\)表示考慮了\(i\)個商店,選擇購買了\(j\)個商店的物品需要花費的最少時間

那麼轉移方程為

\(dp_{i,j}=\min(dp_{i-1,j},dp_{i-1,j-1}+1+(dp_{i-1,j-1}+1)\times a_i+b_i)\)

然後把剩下的\(b\)看看最多能選幾個就行了

eriri
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=2e5+5;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
	};
}using namespace AE86;
int n,T,f[NN][31],cnt,res,lg,ans;
struct node{
	int a,b;
	friend bool operator<(node x,node y){
		return (x.b+1)*y.a<(y.b+1)*x.a;
	}
}s[NN];
namespace WSN{
	inline short main(){
		freopen("eriri.in","r",stdin);
		freopen("eriri.out","w",stdout);
		n=read(); T=read();
		for(int i=1;i<=n;i++)
			s[i].a=read(),s[i].b=read();
		sort(s+1,s+n+1);
		for(int i=n;i;i--) if(!s[i].a) ++cnt;
		sort(s+n-cnt+1,s+n+1,[](node x,node y)->bool{return x.b<y.b;});
		res=n-cnt;
		memset(f,0x3f,sizeof(f));
		for(int i=0;i<=res;i++) f[i][0]=0;
		for(int i=1;i<=res;i++){
			for(int j=1;j<=min(i,30ll);j++) f[i][j]=f[i-1][j];
			for(int j=1;j<=min(i,30ll);j++){
				if(f[i-1][j-1]>1e18) continue;
				if(f[i-1][j-1]+1+(f[i-1][j-1]+1)*s[i].a+s[i].b<=T)
					f[i][j]=min(f[i][j],f[i-1][j-1]+1+(f[i-1][j-1]+1)*s[i].a+s[i].b);
			}
		}
		for(int i=0;i<=min(res,30ll);i++) if(f[res][i]<=T) ans=max(ans,i);
		int sm=0;
		for(int i=res+1;i<=n;i++){
			sm+=s[i].b+1;
			for(int j=1;j<=min(res,30ll);j++)
				if(sm+f[res][j]<=T)
					ans=max(ans,j+i-res);
		}
		write(ans);
		return 0;
	}
}
signed main(){return WSN::main();}

T3 團不過\((yui)\)

眼淚沒能流出來。因為已經哭過很多次了。

考慮在限定每堆石子數目互不相同的前提下,用所有方案數減去先手必敗的方案數

\(p_i=(2^n-1)^{\underline{i}}\),即\(i\)堆石子的總方案數

\(f_i\)表示\(i\)堆石子時先手必敗的方案數

我們考慮讓前\(i-1\)堆石子任意取,通過調整最後一堆石子的數目使得異或和為\(0\)
方案數\(p_{i-1}\)

若前\(i-1\)堆石子異或和為\(0\),因為最後一堆不能取\(0\),這種情況是不合法的
,方案數為\(f_{i-1}\)

若前\(i-1\)堆石子中,有\(i-2\)堆石子異或起來是\(0\),那麼最後一堆石子就只能和另一堆石子數目相同,也是不合法的,方案數為\((i-1)\times(2^n-i+1)\times f_{i-2}\)

於是得到\(f_i=p_{i−1}−f_{i−1}-(i-1)\times(2^n-i+1)\times f_{i-2}\),邊界為
\(f_1=f_2=0\),直接\(O(n)\)遞推即可。

好久沒有見過題解給錯式子了(瘋狂作死)

yui
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7,NN=1e7+5;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
	};
}using namespace AE86;
int n,tot,f[NN],p[NN];
auto qmo=[](int a,int b,int ans=1){
	int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
	return ans;
};
auto inv=[](int x){return qmo(x,mod-2);};
auto prework=[](){
	p[0]=1; f[1]=f[2]=0; int pw2=qmo(2,n);
	for(int i=1;i<=n;i++) p[i]=(pw2-i+mod)%mod*p[i-1]%mod;
	for(int i=3;i<=n;i++) f[i]=p[i-1]-f[i-1]-(i-1)*((pw2-i+1+mod)%mod)%mod*f[i-2]%mod;
};
namespace WSN{
	inline short main(){
		freopen("yui.in","r",stdin);
		freopen("yui.out","w",stdout);
		n=read(); prework();
		write((p[n]-f[n]+mod)%mod);
		return 0;
	}
}
signed main(){return WSN::main();}

T4 七負我\((nanami)\)

既然真白你說不要的話,神田君就歸我了哦。

\(\textit{meet in the middle}\),轉化題意為尋找最大的團

其中團定義為完全圖,也就是找最大的完全子圖

那麼直接列舉一半的完全子圖然後通過兩半之間的連邊尋找最大的完全子圖

然後均分打工時長並計算貢獻即可,證明比較複雜,可以使用調整法然而我也不太會調整法

nanami
#include<bits/stdc++.h>
#define int long long
#define bt __builtin_popcountll
using namespace std;
const int NN=43;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
	};
	auto swap_=[](int&a,int&b){a^=b^=a^=b;};
}using namespace AE86;
int n,m,x,e[NN],ans,lim,f[1<<20],g[NN];
inline bool check(int s,int opt){
	for(int i=1;i<=lim;i++) if(s&(1<<i-1))
		if((s^(1<<i-1)|e[i+opt*lim])!=e[i+opt*lim]) return 0;
	return 1;
}
namespace WSN{
	inline short main(){
		freopen("nanami.in","r",stdin);
		freopen("nanami.out","w",stdout);
		n=read(); m=read(); x=read(); lim=n+1>>1;
		for(int i=1,u,v;i<=m;i++){
			u=read(),v=read();
			if(u>v) swap_(u,v);
			if(v<=lim){
				e[u]|=1<<v-1;
				e[v]|=1<<u-1;
			}else if(u>lim){
				e[u]|=1<<v-1-lim;
				e[v]|=1<<u-1-lim;
			}else g[v]|=1<<u-1;
		}
		for(int i=1;i<(1<<lim);i++) if(check(i,0))
			ans=max(ans,f[i]=bt(i));
		for(int s=1;s<(1<<lim);s++)
			for(int i=1;i<=lim;i++)
				if(!(s&(1<<i-1))) f[s|(1<<i-1)]=max(f[s|(1<<i-1)],f[s]);
		for(int s=1;s<(1<<lim);s++) if(check(s,1)){
			int all=(1<<lim)-1;
			for(int i=1;i<=lim;i++) if(s&(1<<i-1)) all&=g[i+lim];
			ans=max(ans,bt(s)+f[all]);
		}
		double wsn=(1.0*x/ans)*(1.0*x/ans)*(ans-1)*ans/2.0;
		printf("%.6lf\n",wsn);
		return 0;
	}
}
signed main(){return WSN::main();}