11.8 正睿做題筆記
T1
感覺還是比較水的一道題,我們直接用 bitset 硬做就可以了。
程式碼:
#include<bits/stdc++.h> #define dd double #define ld long double #define ll long long #define uint unsigned int #define ull unsigned long long #define N 20100 #define M number using namespace std; const int INF=0x3f3f3f3f; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } bitset<20100> bit[N],b0,b1,b2,b3; char s[N]; int n,m; int main(){ // freopen("my.in","r",stdin); // freopen("my.out","w",stdout); read(n);read(m); for(int i=1;i<=m;i++){ b0.reset();b1.reset();b2.reset();b3.reset(); scanf("%s",s+1); for(int j=1;j<=n;j++){ if(s[j]=='0') b0[j]=1; else if(s[j]=='1') b1[j]=1; else if(s[j]=='2') b2[j]=1; else if(s[j]=='3') b3[j]=1; } for(int j=1;j<=n;j++){ if(s[j]=='0') continue; else if(s[j]=='1') bit[j]=bit[j]^(b2|b3); else if(s[j]=='2') bit[j]=bit[j]^(b1|b3); else if(s[j]=='3') bit[j]=bit[j]^(b1|b2|b3); bit[j][j]=0; } } int Ans=0; for(int j=1;j<=n;j++){ Ans+=bit[j].count(); } assert(Ans%2==0); Ans/=2; printf("%d\n",Ans); return 0; }
T2
這個題本質是 dp 優化,首先我們考慮二分如何 check,我們考慮設 dp \(f_i\) 表示前 \(i\) 個值,在每段都大於等於 \(0\) 的情況下,最多能分成多少段。樸素 dp 是 \(n^2\) 的,我們考慮整個轉移可以用 bit 優化。
程式碼:
#include<bits/stdc++.h> #include<iostream> #define dd double #define ld long double #define ll long long #define uint unsigned int #define ull unsigned long long #define N 200010 #define M number using namespace std; const int INF=0x3f3f3f3f; const dd eps=1e-4; const dd eps2=1e-10; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } template<typename T> inline T Max(T a,T b){return a<b?b:a;} int n,a[N],k; struct BIT{ int p[N]; inline void Init(int n){ for(int i=1;i<=n;i++) p[i]=-1; } inline int lowbit(int x){return x&(-x);} inline void Add(int w,int x){ for(int i=w;i<=n;i+=lowbit(i)) p[i]=Max(x,p[i]); } inline int GetMax(int w){ int res=-INF; for(int i=w;i;i-=lowbit(i)) res=Max(res,p[i]);return res; } }bit; inline void Init(){ read(n);read(k); for(int i=1;i<=n;i++) read(a[i]); } int f[N],d[N]; dd b[N],c[N],rk[N]; inline bool Check(dd mid){ bit.Init(n); // printf("mid=%lf\n",mid); for(int i=1;i<=n;i++) b[i]=1.0*a[i]-mid; // printf("b:");for(int i=1;i<=n;i++) printf("%lf ",b[i]);puts(""); for(int i=1;i<=n;i++) b[i]+=b[i-1]; for(int i=1;i<=n;i++) c[i]=b[i]; sort(c+1,c+n+1);int len=unique(c+1,c+n+1)-c-1; // printf("c:");for(int i=1;i<=n;i++) printf("%lf ",b[i]);puts(""); for(int i=1;i<=n;i++){ int Rank=lower_bound(c+1,c+len+1,b[i])-c; rk[Rank]=b[i];d[i]=Rank; } // printf("d:");for(int i=1;i<=n;i++) printf("%d ",d[i]);puts(""); for(int i=1;i<=n;i++){ if(b[i]>=0) f[i]=1; else{f[i]=-1;continue;} int Ans=bit.GetMax(d[i]); f[i]=Max(f[i],Ans+1); bit.Add(d[i],f[i]); } // printf("f:");for(int i=1;i<=n;i++) printf("%d ",f[i]);puts(""); return f[n]>=k; } inline void Solve(){ dd l=1,r=100; while(r-l>eps){ // printf("%lf\n",l); dd mid=(l+r)/2; if(Check(mid)) l=mid; else r=mid; } printf("%lf\n",l); } int main(){ // freopen("my.in","r",stdin); // freopen("my.out","w",stdout); Init();Solve();return 0; }
T3
這個題是一個結論題,我們首先考慮這樣一件事情,就是說,我們把每個邊看做兩條 “b”,這裡為了區分,我們暫且稱作 "b",下面也是一樣,然後我們考慮這樣一件事情,我們現在對 b 進行染色,每一條邊對應的兩條 b 我們要分別染上顏色 \(0\) 或顏色 \(1\),然後我們考慮對於一個管道方案來說,有多少中染色方案。
我們稱兩種染色方案相同,當且僅當對於第一種染色方案的一個管道 \(x,y\),在第二種染色方案中有一個管道 \(x,y\) 與其對應,且這兩個管道的染色方式相同。
注意到,如果一個管道方案不存在相同的管道,那麼染色方式就是 \(2^{n-1}\),如果存在 \(c\) 對相同的管道,那麼染色方式就是 \(2^{n-1-c}\)
我們發現 \(2^{n-1-c}\) 除以 \(2^{n-1}\) 正好是權值,所以我們只需要算出上面的染色方案,把方案數除以 \(2^{n-1}\) 次方即可。
然後我們如果直接計數這個染色方案並不是很好計算,所以我們考慮看這個染色方案與什麼相對應,我們考慮把染色成 \(0\) 的強制放在左邊,染色成 \(1\) 的強制放在右邊,然後我們發現,一種染色方式就對應著一種邊的連線方式,我們考慮直接計數這個連結方式,關注到這個連結方式唯一的限制是一條邊上的兩個 b 不能相連。
同時注意到,我們每個點都是獨立的,也就是說,總的連結方式應該是關注到每一個點,看與每個點相鄰的邊是怎麼相連的,然後把所有點的乘起來。
對於每個點來說相當於這樣一個問題:有 \(n\) 對點,現在要把這 \(n\) 對點兩兩匹配,或者不匹配,有一些限制,限制是以點對的形式出現的,然後一對點之間不能相互匹配,計數上面這個東西額的方案數。
考慮 dp,設 \(f_{i,0/1/2}\) 表示有 \(i\) 對點,還有 \(0/1/2\) 個單點,其中這 \(i\) 對點之間是限制,問這些點兩兩匹配或不匹配的方案數。
對於 \(f_{i,0}\) 來說,我們考慮最後一對的最後一個點怎麼匹配,首先這個點可能不匹配,這樣方案就是 \(f_{i-1,1}\),或者匹配,但只能選前面這 \(i-1\) 對點對中的點匹配,匹配之後我們把這一對匹配的點扔掉,考慮剩下的點的方案,這樣做是因為不能讓匹配上的點再匹配其他點。由此,我們可以得到:
\[f_{i,0}=f_{i-1,1}+f_{i-2,2}\times 2\times (i-1) \]同理,我們還可以得到:
\[f_{i,1}=f_{i,0}+f_{i-1,1}\times 2\times i\\ f_{i,2}=f_{i,1}+f_{i,0}+f_{i-1,2}\times 2\times i \]預處理完 dp 之後直接轉移即可。
#include<iostream>
#include<cstdio>
#define ll long long
#define N 1000100
#define M number
using namespace std;
const int mod=1e9+7;
int f[N][3],n,ans=1,inv2=500000004,d[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n-1;i++){
int from,to;scanf("%d%d",&from,&to);d[from]++;d[to]++;
ans=1ll*ans*inv2%mod;
}
f[0][0]=1;f[0][1]=1;f[0][2]=2;
for(int i=1;i<=n;i++){
f[i][0]=(f[i-1][1]+1ll*2*(i-1)*f[i-2][2]%mod)%mod;
f[i][1]=(f[i][0]+1ll*f[i-1][1]*2*i%mod)%mod;
f[i][2]=((f[i][1]+f[i][0])%mod+1ll*f[i-1][2]*2*i%mod)%mod;
}
for(int i=1;i<=n;i++) ans=1ll*ans*f[d[i]][0]%mod;
printf("%d",ans);
return 0;
}
這個題啟示我可以嘗試計數相似但好計數的情況,然後考慮轉化。