NOIP模擬85(多校18)
前言
好像每個題目背景所描述的人都是某部番裡的角色,熱切好像都挺慘的(情感上的慘)。
然後我只知道 T1 的莓,確實挺慘。。。
T1 莓良心
解題思路
首先答案只與 \(w\) 的和有關係,於是問題就變成了對於一個點求出每一個所在組的大小以及對應的方案數。
考場上想的是列舉組的大小,然後預處理一下 \(n\) 個數字劃分為 \(m\) 個非空集合的方案數。
一開始想的是隔板法,顯然不對,然後就嘗試 DP 計算,就有了 \(f_{i,j}=f_{i-1,j}\times j+f_{i-1,j-1}\)
一直想矩陣快速冪,看了半天才發現這 TM 兩維,我優化個鬼!!
考完之後才知道這個是第二類斯特林數,然後才發現這玩意可以 NTT
官方題解的做法非常不一樣,對於一對數 \(u,v\) 當兩者被分到一個組中就會多出來 \(w_u+w_v\) 的貢獻。
然後答案就是 \((\sum\limits_{i=1}^n w_i)\times(\;{n \brace k}+(n-1)\times {n-1 \brace k}\;)\)
發現可以直接容斥去求:
\[\displaystyle{n \brace k}=\frac{1}{k!}\sum_{i=0}^k(-1)^i \binom{k}{i}(k-i)^n \]然後 \(\mathcal{O}(n)\) 的線性篩或者 \(\mathcal{O}(nlogn)\)
code
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define f() cout<<"RP++"<<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=1e6+10,M=2e3+10,mod=998244353; int n,m,ans,base,fac[N],ifac[N] ; int power(int x,int y,int p=mod) { int temp=1; while(y) { if(y&1) temp=temp*x%p; x=x*x%p; y>>=1; } return temp; } int C(int x,int y){return fac[x]*ifac[y]%mod*ifac[x-y]%mod;} int STL(int x,int y) { int temp=0; for(int i=0,bas=1;i<=y;i++,bas=-bas) temp=(temp+bas*C(y,i)*power(y-i,x)%mod+mod)%mod; return temp*ifac[y]%mod; } #undef int int main() { #define int long long freopen("ichigo.in","r",stdin); freopen("ichigo.out","w",stdout); n=read(); m=read(); fac[0]=ifac[0]=1; for(int i=1;i<=n;i++) base=(base+read())%mod,fac[i]=fac[i-1]*i%mod; ifac[n]=power(fac[n],mod-2); for(int i=n-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%mod; printf("%lld",base*(STL(n,m)%mod+STL(n-1,m)%mod*(n-1)%mod)%mod); return 0; }
T2 盡梨了
解題思路
比較直接的一個 DP 就是 \(f_{i,j}\) 前 \(i\) 個商店買 \(j\) 個物品的最短時間。
發現在 \(a_i\) 不為 0 時,增長是指數級別的,於是每次轉移只要 \(log\) 次就夠了。
然後就是對於 \(a_i\) 為 0 的情況進行處理了,直接維護一個字首和二分即可。
程式碼實現上有一些小細節。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<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;
int n,m,ans,pos,lim,f[N],pre[N];
struct Node{int a,b;}s[N];
bool comp(Node x,Node y){x.b++;y.b++;if(x.b*y.a!=y.b*x.a)return x.b*y.a<y.b*x.a;return x.b<y.b;}
#undef int
int main()
{
#define int long long
freopen("eriri.in","r",stdin); freopen("eriri.out","w",stdout);
n=read(); m=read(); lim=(int)log2(m)+1; memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++) s[i].a=read(),s[i].b=read();
f[0]=0; sort(s+1,s+n+1,comp); pos=n+1;
for(int i=1;i<=n;i++) if(!s[i].a){pos=i;break;}
for(int i=1;i<=n;i++) s[i].b+=s[i].a+1,s[i].a++;
for(int i=pos;i<=n;i++) pre[i]=pre[i-1]+s[i].b;
for(int i=1;i<pos;i++)
for(int j=min(i,lim);j>=1;j--)
if(f[j-1]<=m) f[j]=min(f[j],f[j-1]*s[i].a+s[i].b);
for(int i=0;i<=min(n,lim);i++)
{
if(f[i]>m) continue;
int l=pos,r=n,temp=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(pre[mid]<=m-f[i]) temp=mid,l=mid+1;
else r=mid-1;
}
ans=max(ans,i+((~temp)?temp-pos+1:0));
}
printf("%lld",ans);
return 0;
}
T3 團不過
解題思路
很妙的一個題。
設 \(p(i)\) 表示 \(i\) 堆石子的方案數 \(p(i)=(2^n-1)^{\underline{i}}\) 。
設 \(f(n)\) 表示 \(n\) 堆石子先手必敗的方案數,轉移考慮在 \(i-1\) 堆石子後再新增一堆與 \(i-1\) 堆異或和相等的石子堆。
也就是 \(p(n-1)\) ,但是如果 \(i-1\) 堆石子異或和已經是 0 那麼顯然是不合法的,需要減去。
還有一種情況就是 \(i-2\) 堆石子的異或和為 0 新加入了兩堆相同的石子,顯然也是不可以的,也需要減去。
因此就有了:
\[f(i)=p(i-1)-f(i-1)-f(i-2)\times(i-1)\times(2^n-i+1) \]直接遞推,然後拿總方案數減去就好了。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<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=1e7+10,mod=1e9+7;
int n,p[N],p2[N],f[N];
#undef int
int main()
{
#define int long long
freopen("yui.in","r",stdin); freopen("yui.out","w",stdout);
n=read(); p2[0]=1; for(int i=1;i<=n;i++) p2[i]=p2[i-1]*2%mod;
p[0]=1; for(int i=1;i<=n;i++) p[i]=p[i-1]*(p2[n]-i)%mod;
for(int i=3;i<=n;i++) f[i]=(p[i-1]-f[i-1]-(i-1)*f[i-2]%mod*(p2[n]-i+1)%mod+2*mod)%mod;
printf("%lld",(p[n]-f[n]+mod)%mod);
return 0;
}
T4 七負我
解題思路
最優的策略就是我們把所有的值都平均分配到一個完全圖中,可以用 調整法 來證明。
然後直接列舉是 \(2^n\) 的無法接受,考慮 \(meet\;in\;the\;middle\)
於是列舉前 \(\frac{n}{2}\) 裡的完全圖,然後看他對於後 \(\frac{n}{2}\) 個點的連邊。
再預處理出後 \(\frac{n}{2}\) 個點所有點集的所包含的完全圖的個數。
然後列舉前 \(\frac{n}{2}\) 個點的點集,然後看他們對於後 \(\frac{n}{2}\) 連邊的並集合並即可。
程式碼裡好像有一個細節錯了,但是錯的那個點小於20,我直接。。。組合拳??
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<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=50;
int n,m,m1,m2,all,maxn,e[N],f[1<<20];
#undef int
int main()
{
#define int long long
freopen("nanami.in","r",stdin); freopen("nanami.out","w",stdout);
n=read(); m=read(); all=read(); m1=n>>1; m2=n-m1; if(n<=20) m1=n;
for(int i=1,x,y;i<=m;i++) x=read(),y=read(),e[x]|=1ll<<y-1,e[y]|=1ll<<x-1;
for(int sta=1;sta<(1ll<<m1);sta++)
{
int sum=__builtin_popcount(sta); if(sum<=maxn) continue;
for(int i=1;i<=m1;i++) if((sta>>i-1)&1) if((e[i]&sta)!=(sta^(1ll<<i-1))) goto X;
maxn=max(maxn,sum);X:;
}
if(n<=20) printf("%.6lf",(1.0*(maxn*(maxn-1)/2))*(1.0*all)/(1.0*maxn)*(1.0*all)/(1.0*maxn)),exit(0);
for(int sta=1;sta<(1ll<<m2);sta++)
{
int sum=__builtin_popcount(sta);
for(int i=1;i<=m2;i++) if((sta>>i-1)&1) if(((e[i+m1]>>m1)&sta)!=(sta^(1ll<<i-1))) goto Y;
f[sta]=sta; maxn=max(maxn,sum); Y:;
}
for(int sta=0;sta<(1ll<<m2);sta++)
{
int U=(1ll<<m2)-1,sum=__builtin_popcount(sta);
for(int i=1;i<=m2;i++) if((sta>>i-1)&1) U&=e[i+m1]>>m1;
if(!U&&sum!=1) continue;
for(int i=1;i<=m2;i++)
if((((sta>>i-1)&1)^1))
if((((e[i+m1]>>m1)&sta)==sta&&(U&(1ll<<i-1)))){if(__builtin_popcount(f[sta|(1ll<<i-1)])<sum+1) f[sta|(1ll<<i-1)]=f[sta]|(1ll<<i-1);}
else if(__builtin_popcount(f[sta|(1ll<<i-1)])<sum) f[sta|(1ll<<i-1)]=f[sta];
}
for(int sta=1;sta<(1ll<<m1);sta++)
{
int U=(1<<m2)-1,sum=__builtin_popcount(sta);
for(int i=1;i<=m1;i++) if((sta>>i-1)&1) if((e[i]&sta)!=(sta^(1ll<<i-1))) goto Z;
for(int i=1;i<=m1;i++) if((sta>>i-1)&1) U&=e[i]>>m1;
maxn=max(maxn,sum+__builtin_popcount(f[U])); Z:;
}
printf("%.6lf",(1.0*(maxn*(maxn-1)/2))*(1.0*all)/(1.0*maxn)*(1.0*all)/(1.0*maxn));
return 0;
}