Wannafly挑戰賽26 題解

Wannafly挑戰賽26

題目連接

https://www.nowcoder.com/acm/contest/212#question

A. 御阪網絡

枚舉圓心所在的位置,O(n)O(n) 檢查即可,總時間複雜度爲O(n2)O(n^2)

B. 冥土追魂

這題比較坑,我感覺題意敘述有問題,總之也是一道水題,題解略去.

C. 七彩線段

題解

考慮到只有77種顏色,因此可以枚舉最後選出線段的顏色組合,272^7種情況.

線段選法類似於會議安排,對於兩個顏色相同的線段,我們必然優先選擇右端點小的,因此我們第一步需要對線段以右端點從小到大進行排序.

預處理出數組pre[i]pre[i],表示與線段ii不想交的右端點最大的線段是誰.

然後考慮狀態壓縮dpdp:

dp[i][S]dp[i][S]表示考慮前ii個線段,已經選出來的線段顏色組合爲SS,所取得的最大長度.

轉移方程dp[i][S(1<<color[i])]=max(dp[pre[i]][S],dp[i1][S(1<<color[i])])dp[i][S | (1 << color[i])] = max(dp[pre[i]][S],dp[i-1][S(1<<color[i])])

代碼

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
#define clr(x) memset(x,0,sizeof(x))
#define setinf(x) memset(x,0x3f,sizeof(x))
struct seg{
    int l,r,c;
    bool operator<(const seg& sg)const{
        return r < sg.r;
    }
};
std::vector<seg> segs;
int n,m;
long long dp[100007][1<<7];
int used[1 << 7];

inline int cnt(int x) {
    int res = 0;
    while (x) {
        res++;
        x -= x & -x;
    }
    return res;
}
int pre[100007];
int main() {
    std::cin >> n >> m;
    for(int S = 0;S < (1<<7);++S) {
        if(cnt(S) == m) used[S] = 1;
    }
    for(int i = 0;i < n;++i) {
        int l,r,c;
        std::cin >> l >> r >> c;
        c--;
        segs.push_back((seg){l,r,c});
    }
    std::sort(segs.begin(),segs.end());
    for(int i = 0;i < n;++i) {
        int id = (std::lower_bound(segs.begin(),segs.end(),(seg){0,segs[i].l,0}) - segs.begin());
        --id;
        pre[i] = id;
    }
    long long ans = -1;
    dp[0][1<<segs[0].c] = segs[0].r - segs[0].l;
    for(int i = 1;i < n;++i) {
		dp[i][1 << segs[i].c] = segs[i].r - segs[i].l;
        for(int S = 0;S < (1<<7);++S) {
            dp[i][S] = std::max(dp[i][S],dp[i-1][S]);
        }
        if(pre[i] >= 0)
        for(int S = 0;S < (1 << 7);++S) {
            int nS = S|(1<<segs[i].c);
			if(dp[pre[i]][S])
				dp[i][nS] = std::max(dp[i][nS],dp[pre[i]][S] + segs[i].r - segs[i].l);
        }
		for(int S = 0;S < (1 << 7);++S) {
			if(used[S] && dp[i][S] > ans)
			   ans = dp[i][S];	
		}
    }
    if(ans == 0) ans = -1;
    std::cout << ans << std::endl;
}

D.禁書目錄

題解

我們考慮每種顏色被在排列中被計數了多少次.

[0]結論: 一本書不會消失當且僅當所有aa大於等於它的書都在它的右邊.

因此假設有tt本書其aiaxa_i \ge a_x,只考慮這tt本書的排列,書xx被看見的概率是1t\frac{1}{t}.

[1]假設有書ax&gt;aya_x &gt; a_y,且aiaxa_i \ge a_x的有txt_x本書,aiaya_i \ge a_y的有tyt_y本書,顯然tx&lt;tyt_x \lt t_y,我們希望求出xxyy都沒有出現的概率:

txt_x本書的相互排列中,書xx必然不能出現在第一個位置,這樣概率是tx1tx\frac{t_x-1}{t_x}.然後tyt_y本書中yy也不能出現在第一個位置,txt_x的排列對書yy的選擇沒有影響,因此概率是ty1ty\frac{t_y-1}{t_y},乘起來就是ty1tytx1tx\frac{t_y-1}{t_y}*\frac{t_x-1}{t_x}.

[2]假設有書ax=aya_x = a_y,且aiaxa_i \ge a_x的書有tt本,我們希望求出書xxyy都沒有出現的概率.先不考慮axa_x,那麼aya_y不出現的概率是t2t1\frac{t-2}{t-1},再考慮axa_x不出現的概率是t1t\frac{t-1}{t},乘起來就是t2t\frac{t-2}{t},可以猜測有kk本書aa相同時,且aiaa_i \ge a的書有tt本,那麼這kk本書都沒出現的概率是tkt\frac{t-k}{t}

ps:我們爲什麼要求[2]呢,爲什麼ax=aya_x = a_y時候,求兩者都不出現的概率時候不能直接使用[0]結論呢?
這是因爲
當對axa_x使用結論[0]時候,默認aya_yaxa_x右側,而再對aya_y使用結論[0]時候,默認axa_xaya_y右側,這樣就出現了矛盾,因此,當兩者aa相等時,就不能直接用結論00了,而需要擴展一下.

結合[1][2]兩個結論,我們枚舉每一種顏色,計數這些顏色的書每一本都沒有被看到的概率.
然後最後用11減去這個概率再乘以n!n!即是這部分顏色的貢獻.

舉個例子,當顏色爲cc的書爲
a1&lt;a3=a5&lt;a6&lt;a7a_1 &lt; a_3 = a_5 &lt; a_6 &lt; a _ 7 時候是,對答案的貢獻就是

n!(1t11t1t32t3t61t6t71t7)n! * (1-\frac{t_1-1}{t_1}*\frac{t_3-2}{t_3}*\frac{t_6-1} {t_6}*\frac{t_7-1}{t_7})

其中tit_i表示不小於aia_i的書的本數.

代碼

#include <iostream>
#include <algorithm>
#include <map>

#define pr(x) std::cout << #x << ":" << x << std::endl

const int N = 1000007;

typedef long long ll;
typedef std::pair<int,int> pii;
const ll P = 998244353;
std::map<int,ll> mp;
ll mod_pow(ll x,ll n) {
    ll res = 1;
    while(n) {
        if(n & 1) res = res * x % P;
        x = x * x % P;
        n >>= 1;
    }
    return res;
}
int n;
pii ps[N];
int main() {
    std::ios::sync_with_stdio(false);
    std::cin >> n;
    for(int i = 1;i <= n;++i) {
        int a,b;
        std::cin >> a >> b;
        ps[i-1] = (pii){a,b};
    }
    std::sort(ps,ps+n);
    ll ans = 0;
    ll nn = 1;
    for(int i = 1;i <= n;++i) 
        nn = nn * i % P;
	int last = 0;
    for(int i = 0;i < n ;++i) {
        int pos = i;
        while(pos < n-1 && ps[pos] == ps[pos+1])
            ++pos;
        if(mp.count(ps[pos].second) == 0) 
            mp[ps[pos].second] = 1;
		if(ps[i].first != ps[last].first) last = i;
        ll big = n - last;
		mp[ps[pos].second] = mp[ps[pos].second] * (big - (pos - i + 1)) % P
            * mod_pow(big,P-2) % P;
        i = pos;
    }
    for(auto &p : mp) {
        ans = (ans + (nn * (1 + P - p.second) % P)) % P;
    }
    std::cout << ans << std::endl;
}

E.螞蟻開會

待解決

F.msc的棋盤

題解

這其實是一道現尋找充要條件,然後使用dpdp計數的題.

如果給出aa數組(行數組),和bb數組(列數組),要進行判定,那麼我們想到了用網絡流進行判定,如果滿流的話,就表示判定成功.

n=4,m=2,b[1]=1,b[2]=3n = 4,m = 2,b[1] = 1,b[2] = 3時候,
左邊一排點有44個,右邊一排點有22個,且兩排點之間兩兩有邊容量爲11.源點向第一排點連邊容量爲a[i]a[i],第二排點向匯點連邊,容量爲b[i]b[i].

sum=b[i]sum = \sum{b[i]}

根據最大流最小割定理,也就是說圖的最小割必然要=sum= sum

考慮一個割選取了左邊xx個點,右邊yy個點,那麼必然會選擇左邊a[i]a[i]最小的前xx個點,同理右邊會選擇b[i]b[i]最小的前yy個點.同樣在剩下的沒有選擇的邊中中間容量爲11的邊都要被切掉.

sa,sbsa,sb表示a,ba,b排好序的前綴和.

(x,y)=sa[x]+sb[y]+(nx)(my)sum割(x,y) = sa[x] + sb[y] + (n-x)(m-y) \ge sum

且由於最大流sum\le sum,所以保證了有=sum割=sum.

因此我們就得到了一個充要條件.

那就是所有的必然要sum\ge sum,求方案數.

相當於要把sumsum個棋子,分給每一行,使得滿足sum\ge sum,的方案數.直覺告訴我們要用dpdp來做.

定義dp[i][j][k]dp[i][j][k]表示考慮a[i]a[i]ii小的行,且最大行的a[i]ja[i] \le j,且sa[i]=ksa[i] = k的方案數.

轉移方程:

dp[i+t][j+1][k+t(j+1)]+=dp[i][j][k]Cnit,0tnidp[i+t][j+1][k+t(j+1)] += dp[i][j][k]C_{n-i}^{t},且0 \le t \le n-i

觀察dpdp方程,只有第二維嚴格遞增,因此轉移的時候我們先枚舉第二維,然後再枚舉第一維和第三維,這樣保證了dpdp的無後效性.

代碼

#include <cstdio>
#include <iostream>
#include <algorithm>


#define pr(x) std::cout << #x << ":" << x << std::endl
typedef long long ll;
const ll P = 1000000007;
const int N = 51;
int n,m;
ll dp[N][N][N*N]; 
// dp[i][j][k] 表示前i小的行都已經考慮完,第i小的行有j個棋子,且前i行總棋子數量爲k的可能的方案數.

int sa[N],sb[N];
//sa[i]表示前i小的行棋子總數的最小限度
ll C[N][N];
void init() {
	C[0][0] = 1;
	for(int i = 1;i < N;++i) {
		C[i][0] = 1;
		for(int j = 1;j <= i;++j) {
			C[i][j] = (C[i-1][j-1] + C[i-1][j]) % P;
		}
	}
}
ll fC(int n,int m) {
	if(m > n || m < 0) return 0;
	return C[n][m];
}
void add(ll &x,ll y) {
	x = x + y;
	if(x > P) x -= P;
}

int main() {
	init();
	std::cin >> n >> m;
	for(int i = 1;i <= m;++i) 
		std::cin >> sb[i];

	std::sort(sb+1,sb+1+m);
	
	for(int i = 1;i <= m;++i)
		sb[i] += sb[i-1];
	
	for(int i = 1;i <= n;++i) {
		int mi = 2500;
		for(int j = 1;j <= m;++j) {
			mi = std::min(mi,sb[j] + (n-i)*(m-j));
		}
		sa[i] = sb[m] - mi;
	}
	
	int lim = sb[m];

	for(int i = 0;i <= n && 0 >= sa[i];++i) {
		dp[i][0][0] = fC(n,i);
	}
	for(int j = 0;j <= m;++j) {
		for(int i = 0;i <= n;++i) {
			for(int k = 0;k <= lim;++k) {
				if(dp[i][j][k] == 0) continue;
				for(int t = 0;i+t <= n && k + t*(j+1) <= lim
						&& k + t*(j+1) >= sa[i+t];++t) {
					add(dp[i+t][j+1][k+t*(j+1)],dp[i][j][k]*fC(n-i,t)%P);
				}
			}
		}
	}
	std::cout << dp[n][m][lim] << std::endl;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章