Wannafly挑戰賽26
題目連接
https://www.nowcoder.com/acm/contest/212#question
A. 御阪網絡
枚舉圓心所在的位置, 檢查即可,總時間複雜度爲
B. 冥土追魂
這題比較坑,我感覺題意敘述有問題,總之也是一道水題,題解略去.
C. 七彩線段
題解
考慮到只有種顏色,因此可以枚舉最後選出線段的顏色組合,種情況.
線段選法類似於會議安排,對於兩個顏色相同的線段,我們必然優先選擇右端點小的,因此我們第一步需要對線段以右端點從小到大進行排序.
預處理出數組,表示與線段不想交的右端點最大的線段是誰.
然後考慮狀態壓縮:
表示考慮前個線段,已經選出來的線段顏色組合爲,所取得的最大長度.
轉移方程
代碼
#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]結論: 一本書不會消失當且僅當所有大於等於它的書都在它的右邊.
因此假設有本書其,只考慮這本書的排列,書被看見的概率是.
[1]假設有書,且的有本書,的有本書,顯然,我們希望求出和都沒有出現的概率:
本書的相互排列中,書必然不能出現在第一個位置,這樣概率是.然後本書中也不能出現在第一個位置,的排列對書的選擇沒有影響,因此概率是,乘起來就是.
[2]假設有書,且的書有本,我們希望求出書和都沒有出現的概率.先不考慮,那麼不出現的概率是,再考慮不出現的概率是,乘起來就是,可以猜測有本書相同時,且的書有本,那麼這本書都沒出現的概率是
ps:我們爲什麼要求[2]呢,爲什麼時候,求兩者都不出現的概率時候不能直接使用[0]結論呢?
這是因爲
當對使用結論[0]時候,默認在右側,而再對使用結論[0]時候,默認在右側,這樣就出現了矛盾,因此,當兩者相等時,就不能直接用結論了,而需要擴展一下.
結合[1][2]兩個結論,我們枚舉每一種顏色,計數這些顏色的書每一本都沒有被看到的概率.
然後最後用減去這個概率再乘以即是這部分顏色的貢獻.
舉個例子,當顏色爲的書爲
時候是,對答案的貢獻就是
其中表示不小於的書的本數.
代碼
#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的棋盤
題解
這其實是一道現尋找充要條件,然後使用計數的題.
如果給出(行數組),和(列數組),要進行判定,那麼我們想到了用網絡流進行判定,如果滿流的話,就表示判定成功.
如時候,
左邊一排點有個,右邊一排點有個,且兩排點之間兩兩有邊容量爲.源點向第一排點連邊容量爲,第二排點向匯點連邊,容量爲.
設
根據最大流最小割定理,也就是說圖的最小割必然要
考慮一個割選取了左邊個點,右邊個點,那麼必然會選擇左邊最小的前個點,同理右邊會選擇最小的前個點.同樣在剩下的沒有選擇的邊中中間容量爲的邊都要被切掉.
用表示排好序的前綴和.
且由於最大流,所以保證了有.
因此我們就得到了一個充要條件.
那就是所有的必然要,求方案數.
相當於要把個棋子,分給每一行,使得滿足,的方案數.直覺告訴我們要用來做.
定義表示考慮前小的行,且最大行的,且的方案數.
轉移方程:
觀察方程,只有第二維嚴格遞增,因此轉移的時候我們先枚舉第二維,然後再枚舉第一維和第三維,這樣保證了的無後效性.
代碼
#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;
}