Atcoder Dwango Programming Contest 6th 题解

\(A\) \(Falling\) \(Asleep\)

题目链接

\(description:\)

给你一个歌单\(,\) \(n\)首歌的歌名\(s_i\)和播放持续时间\(t_i\) \(,\)

读入一个歌名\(st,\)要求在歌单中算出在这首曲子之后所有曲子的播放时间总和\(.\)

\(solution:\)

暴力即可\(.\) 时间复杂度 \(O(n + \sum|s_i|)\)

\(code:\)

#include <bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0',c = getchar();
    return x;
}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}
inline void write(int x){if (x > 9) write(x/10); putchar(x%10+'0'); }
int n; string st,s[500]; int t[500];
int main(){
	cin >> n;
	for (int i = 1; i<= n; ++i) cin >> s[i] >> t[i];
	cin >> st; 
	int id = -1; long long ans = 0;
	for (int i = 1; i <= n; ++i) if (s[i] == st) id = i;
	for (int i = id+1; i <= n; ++i) ans += t[i];
	cout << ans << endl;
    return 0;
}

\(B\) \(Fusing\) \(Slimes\)

题目链接

\(description :\)

\(n(n≤10^5)\)块石子\(,\)每个石子有其初始坐标\(x_i.\)

每次你会从中随机选出不在最右边的一堆\(,\)将这一堆移动到 它右边最靠近它的一堆石子的位置\(,\)并把这两堆石子合并为一堆\(.\)

求出所有情况下\(,\)石子移动距离的总和 \(,\) 即期望乘以 \((n-1)!\) \(.\)

答案对\(P = 1e9 + 7\) 取模\(.\)

\(solution :\)

考虑一段距离\(d_i = x_{i+1} - x_i\)

定义 $dp[n] = $ 一段距离之前有\(n\)块石子\(,\)把这\(n\)块石子移动过去之后\(,\)这段距离被经过的期望次数

不难得出 \(dp[i] = dp[i-1] + 1/i\)

然后就可以直接计算出每一段距离对答案的贡献了\(.\)

时间复杂度\(O(n).\)

\(code :\)

#include <bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0',c = getchar();
    return x;
}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}
inline void write(int x){if (x > 9) write(x/10); putchar(x%10+'0'); }
const int P = 1e9 + 7,N = 500050;
int n,fac[N],inv[N];
int x[N];
int f[N];
long long ans,now;
int main(){
	int i;
	read(n);
	for (i = 1; i <= n; ++i) read(x[i]);
	for (fac[0] = i = 1; i <= n; ++i) fac[i] = 1ll*fac[i-1]*i%P;
	for (inv[0] = inv[1] = 1,i = 2; i <= n; ++i) inv[i] = 1ll*inv[P%i]*(P-P/i)%P;
	for (i = 1; i <= n; ++i){
		int prob; prob = inv[i];
		f[i] = 1ll*prob*(f[i-1]+1) % P;
		f[i] += 1ll*(P+1-prob)*f[i-1] % P;
		f[i] %= P;
	}
	ans = 0;
	for (i = 1; i <= n-1; ++i){
		int dist = x[i+1] - x[i];
		ans = (ans + 1ll*dist*f[i]%P)%P;
	} 
	cout << 1ll*fac[n-1]*ans%P << endl;
    return 0;

\(C\) \(Cookie\) \(Distribution\)

题目链接

$description : $

\(n\) 个人 \(,\)\(k\) 天中你要给他们发糖果 \(.\)

读入 \(a_1 .. a_k,\) 其中 \(a_i\) 表示第\(i\)天会从\(n\)个人当中均匀随机选出 \(a_i\) 个人 \(,\) 并给他们每人发一颗糖 \(.\)

\(c_i\)为第\(i\)个人发到的糖果个数\(,\)\(\Pi c_i\)的期望\(.\)

\(n<=10^3,k<=20\)

\(solution:\)

\(\prod c_i\) 相当于求 从 \(n\) 个人中每个人手里选一颗糖的方案数

考虑枚举 \(x_i\) 表示从第 \(i\) 个人手里选的糖果是从哪一天来的 \(.\)

\(x_i\) 是什么并不重要\(,\)重要的是\(cnt2_i :\) 表示有多少\(x_j\) \(=\) \(i\)

这种选法对答案的贡献是

\[\large \prod^{k}_{i=1} C^{a_k-cnt2_k}_{n-cnt2_k} \times \prod_{i=1}^{k} (cnt2_i!)^{-1} \times n! \]

然后用一个\(O(nk^2) dp\)就可以解决这个问题了\(.\)

\(code :\)

#include <bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0',c = getchar();
    return x;
}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}
inline void write(int x){if (x > 9) write(x/10); putchar(x%10+'0'); }

const int N = 1050,K = 20 + 5,P = 1e9 + 7;
int n,k,a[K];
int fac[N],nfac[N],inv[N];
inline int C(int n,int m){ return (n<m||n<0||m<0) ? 0 : 1ll*fac[n]*nfac[m]%P*nfac[n-m]%P; }
int t[K][N];
int dp[K][N];
int main(){
	int i,j,e;
	read(n),read(k); for (i = 1; i <= k; ++i) read(a[i]);
	inv[0] = fac[0] = nfac[0] = inv[1] = fac[1] = nfac[1] = 1;
	for (i = 2; i <= n; ++i){
		fac[i] = 1ll*fac[i-1]*i%P;
		inv[i] = 1ll*(P-P/i)*inv[P%i]%P;
		nfac[i] = 1ll*nfac[i-1]*inv[i]%P;
	} 
	for (i = 1; i <= k; ++i)
	for (j = 0; j <= n; ++j) t[i][j] = 1ll*C(n-j,a[i]-j)*nfac[j]%P;
	dp[0][0] = 1;
	for (i = 1; i <= k; ++i)
	for (j = 0; j <= n; ++j){
		dp[i][j] = 0;
		for (e = 0; e <= j; ++e) dp[i][j] = (dp[i][j] + 1ll * t[i][e] * dp[i-1][j-e]) % P;
	}
	int ans = 1ll * dp[k][n] * fac[n] % P; 
    cout << ans << endl;
	return 0;
}

\(D\) \(Arrangement\)

题目链接

\(description :\)

给你一张图\(G,\) \(G\)\(n\)个点\(,\) \(n*(n-2)\) 条有向边\(.\)

\(G\)的补图是一个所有点出度\(=1\) \((\) 可能有自环 \()\) 的有向图\(.\)

求出一条字典序最小的哈密尔顿路径\(,\)如果不存在则输出\(-1.\)

\(n<=10^5\)

\(solution :\)

首先如果规模足够小\((n<=8)\)就可以直接暴力\(O(n!)\)

然后我们考虑对于\(n\)更大的情况来处理答案\(.\)

记$cnt_i = $ 当前的图中\(,\) 有多少\(a_j = i\)

如果存在一个点\(p\)满足\(cnt_p = n-1\)那么我们必须选这个点作为路径的第一个点\(.\)

如果不存在这样的一个点\(,\)我们就可以选择当前的点当中编号最小的点\(.\)

然后问题就变成了一个规模为\(n-1\)的子问题\(,\)只是多了一个开头不能为某个数的限制\(.\)

那么我们就可以写一个支持 查询\(cnt\)最大值\(,\) \(cnt\)单点修改 和 查询当前点集的编号最小者\(,\) 删除一个点的数据结构\(,\) 再写一个\(O(n!)\)的暴力即可\(.\)

复杂度\(O(8! +nlogn)\)

\(code:\)

#include <bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0',c = getchar();
    return x;
}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}
inline void write(int x){if (x > 9) write(x/10); putchar(x%10+'0'); }
const int N = 100050;
int n,a[N];

namespace subtask0{
	int ans[100],vis[100]; bool ok;
	int p[100];
	inline void chk(){
		for (int i = 2; i <= n; ++i) if (a[ans[i-1]] == ans[i]) return;
		ok = 1; for (int i = 1; i <= n; ++i) p[i] = ans[i];
		return; 
	}
	inline void dfs(int dep){
		if (ok) return;
		if (dep > n){ chk(); return; }
		for (int i = 1; i <= n; ++i) if (!vis[i]){
			vis[i] = 1,ans[dep] = i;
			dfs(dep+1);
			vis[i] = 0;
		}
	}
	inline void solve(){
		ok = 0; memset(vis,0,sizeof(vis)); memset(ans,0,sizeof(ans)); dfs(1);
		if (!ok){ puts("-1"); return; }
		for (int i = 1; i <= n; ++i) cout << p[i] << ((i<n) ? (' '):('\n'));
	}
}

int mx[N<<2],mxi[N<<2],data[N<<2],cnt[N];
inline void Build(int o,int l,int r){
	if (l^r){
		int mid = l+r>>1; Build(o<<1,l,mid); Build(o<<1|1,mid+1,r);
		mx[o] = max(mx[o<<1],mx[o<<1|1]); data[o] = min(data[o<<1],data[o<<1|1]);
		mxi[o] = mxi[ (mx[o<<1]>mx[o<<1|1]) ? (o<<1) : (o<<1|1) ];
		return;
	}
	mx[o] = cnt[l]; data[o] = (mx[o] >= 0) ? (l) : (n+1); mxi[o] = l;
}
inline int Ask(int o,int l,int r,int p){
	if (l==r) return mx[o];
	int mid = l+r>>1; return (p<=mid) ? Ask(o<<1,l,mid,p) : Ask(o<<1|1,mid+1,r,p);
} 
inline void Add(int o,int l,int r,int p,int v){
	if (l==r){ mx[o]=v; data[o] = (mx[o] >= 0) ? (l) : (n+1); return; }
	int mid = l+r>>1; if (p<=mid) Add(o<<1,l,mid,p,v); else Add(o<<1|1,mid+1,r,p,v);
	mx[o] = max(mx[o<<1],mx[o<<1|1]); data[o] = min(data[o<<1],data[o<<1|1]);
	mxi[o] = mxi[ (mx[o<<1]>mx[o<<1|1]) ? (o<<1) : (o<<1|1) ];
}
inline int Nowv(){ return data[1]; }
inline int Query(int pos){ if (pos==0) return -1; return Ask(1,1,n,pos); }
inline void Modify(int x,int v){ cnt[x] = v; Add(1,1,n,x,v); }


int ans[N];
int vis[N],nowc[N],lc;
int qwq;
inline void chk(){
	for (int i = qwq; i <= n; ++i) if (ans[i]==a[ans[i-1]]) return;
	for (int i = 1; i <= n; ++i) write(ans[i]),putchar((i<n)?(' '):('\n'));
	exit(0);
}
inline void dfs(int dep){
	if (dep>n){ chk(); return; }
	for (int i = 1; i <= lc; ++i) if (!vis[i]){
		vis[i] = 1;
		ans[dep] = nowc[i];
		dfs(dep+1);
		vis[i] = 0;
	}
}
inline void solve_force(int l,int r){
	qwq = l;
	for (int i = 1; i <= n; ++i) if (Query(i) >= 0) nowc[++lc] = i,vis[lc] = 0;
	dfs(l);
}
inline bool unproperty(){
	for (int i = 1; i <= n; ++i) cnt[i] = 0;
	for (int i = 1; i <= n; ++i) ++cnt[ans[i]];
	for (int i = 1; i <= n; ++i) if (cnt[i] != 1) return 1;
	for (int i = 2; i <= n; ++i) if (ans[i] == a[ans[i-1]]) return 1;
	return 0;
}
int main(){
	int i,banp,ret,siz,p;
	int pp,val;
	read(n);
	for (i = 1; i <= n; ++i) read(a[i]);
	for (i = 1; i <= n; ++i) if (a[i]==i) a[i]=0;
	for (i = 1; i <= n; ++i) ++cnt[a[i]];
//	if (n==2){ puts("-1"); return 0; }
	if (n<=8){ subtask0::solve(); return 0; }
	
	Build(1,1,n);
	for (banp = 0,siz = n,i = 1; i <= n; ++i,--siz){
		if (siz <= 6){
			solve_force(i,n); 
		}
		banp = a[ans[i-1]];
		if (banp != 0 && (ret=Query(banp)) >= 0){
			Modify(banp,-1);
			if (mx[1] == siz-1){
				p = mxi[1];
				ans[i] = p;
				Modify(ans[i],-1);
			}
			else{
				p = data[1];
				ans[i] = p;
				Modify(ans[i],-1);
			}
			pp = a[ans[i]];
			if ((val=Query(pp))>=0){ --val; Modify(pp,val); }
			Modify(banp,ret);
		}
		else{
			if (mx[1] == siz-1){
				p = mxi[1];
				ans[i] = p;
				Modify(ans[i],-1);
			}
			else{
				p = data[1];
				ans[i] = p;
				Modify(ans[i],-1);
			}
			pp = a[ans[i]];
			if ((val=Query(pp))>=0){ --val; Modify(pp,val); }
		}
	}
	if (unproperty()){ puts("-1"); return 0; }
	for (i = 1; i <= n; ++i) write(ans[i]),putchar((i<n)?(' '):('\n'));
}

\(E\) \(Span\) \(Covering\)

题目链接

\(description:\)

\(n\)条长度为\(l_i\)的线段\(.\)

你要把这些线段放到一个长为\(X\)的坐标轴上 \((\) \(n <=100\) \(X <= 500\) \()\)

并且满足一个条件\(:\) 不能覆盖到外面\(,\)也不能有地方没有被任何一条线段覆盖到\(.\)

求方案数\(.\)

\(solution:\)

首先把\(l_i\)从大到小排序\(.\)

考虑一个\(dp:\)

$f[i][j][k] = $ 前\(i\)个区间组成了\(j\)个相邻的开区间\(,\)其总长度为\(k\)的方案数\(.\)

转移有三种\(:\)

\(1.\) 新开一个区间\(;(j->j+1)\)

\(2.\) 把当前的某一个区间和我新加进去的区间合并为一个区间\(;(j->j)\)

\(3.\) 把两个相邻的区间通过我新加进去的区间合并成一个区间\(.(j->j-1)\)

其中第\(1\)种转移的长度是确定的\(,\)\(k + len\)

\(2\) \(3\)种转移的长度是需要枚举的\(.\)

看起来复杂度是\(O(n^2X^2),\)但是在枚举到\(i\)的时候\(,\)只有\(k*l_i <= j\)的状态是有用的\(.\)

所以复杂度为\(O(???\) 能过 \()\) \((\) 好像是 \(O(nX^2)?\) \()\)

\(code:\)

#include <bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0',c = getchar();
    return x;
}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}
inline void write(int x){if (x > 9) write(x/10); putchar(x%10+'0'); }
const int N = 100 + 5,M = 1050,P = 1e9 + 7;
int n,m;

int f[N][M],g[N][M];
inline void upd(int &x,int y){ x+=y; x>=P?x-=P:0; x<0?x+=P:0; }
inline void DP(int L){
	int i,j,k;
//	cout <<"DP " << endl;
//	for (i = 1; i <= n; ++i,cout << endl)
//	for (j = 1; j <= m; ++j) cout <<f[i][j] << ' ';
	
	for (i = 1; i <= n; ++i)
	for (j = 1; j <= m; ++j) g[i][j] = f[i][j],f[i][j] = 0;
	for (i = 1; i <= n; ++i)
	for (j = 1; j <= m; ++j) if (g[i][j]>0){
		upd(f[i+1][j+L],1ll*(i+1)*g[i][j]%P);
		upd(f[i][j],1ll*g[i][j]*(P+j-i*(L-1))%P);
		for (k = 1; k < L; ++k) upd(f[i][j+k],2ll*g[i][j]%P*i%P);
		for (k = 0; k < L-1; ++k) upd(f[i-1][j+k],1ll*g[i][j]*(L-k-1)%P*(i-1)%P);
	}
}
int a[N];
int main(){
	int i,j;
	int rm;
	read(n),read(m); for (i = 1; i <= n; ++i) read(a[i]),++a[i]; rm = m; m += 1;
	sort(a+1,a+n+1); reverse(a+1,a+n+1);
	memset(f,0,sizeof(f)); f[1][a[1]] = 1;
	for (i = 2; i <= n; ++i) DP(a[i]);
	
//	cout <<"DP " << endl;
//	for (i = 1; i <= n; ++i,cout << endl)
//	for (j = 1; j <= m; ++j) cout <<f[i][j] << ' ';
	int ans = 0;
	for (i = 1; i <= 1; ++i) ans += f[i][rm+i],ans %= P;
    cout << ans << endl;
	return 0;
}
posted @ 2020-08-29 11:14  srf  阅读(320)  评论(0编辑  收藏  举报