1. 程式人生 > 其它 >11.8 正睿做題筆記

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\) 倍。

我們發現 \(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;
} 

這個題啟示我可以嘗試計數相似但好計數的情況,然後考慮轉化。