暑假集训6

前两题只会打暴力,本来以为又要垫底了,结果还可以?

A. 接力比赛

确实是背包,排序后每次跑上界为\(\sum w_i\),然后刚刚好卡过??

随机数据跑的还是挺快的

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<iostream>
#include<set>
#include<map>

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = 1005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
inline int read(){
	int x = 0; char c = getchar(); bool f = 0;
	while(c < '0' || c > '9'){if(c == '-')f = 1;c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	if(f)x = -x; return x;
}
ll f[maxn * maxn], g[maxn * maxn], sum[maxn];
int n, m;
struct node{int v, w;}d[maxn];
bool cmp(node x, node y){return x.w < y.w;}
int main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	n = read(); m = read();
	for(int i = 1; i <= n; ++i)d[i].w = read(), d[i].v = read();
	sort(d + 1, d + n + 1, cmp);
	for(int i = 1; i <= n; ++i)sum[i] = sum[i - 1] + d[i].w;
	for(int i = 1; i <= sum[n]; ++i)f[i] = -inf;
	for(int i = 1; i <= n; ++i)
		for(int j = sum[i]; j >= d[i].w; --j)
			f[j] = max(f[j - d[i].w] + d[i].v, f[j]);
	
	for(int i = 1; i <= m; ++i)d[i].w = read(), d[i].v = read();
	sort(d + 1, d + m + 1, cmp);
	for(int i = 1; i <= m; ++i)sum[i] = sum[i - 1] + d[i].w;
	for(int i = 1; i <= sum[m]; ++i)g[i] = -inf;
	for(int i = 1; i <= m; ++i)
		for(int j = sum[i]; j >= d[i].w; --j)
			g[j] = max(g[j - d[i].w] + d[i].v, g[j]);
	ll ans = 0;
 	for(int i = 1; i <= sum[m]; ++i)ans = max(ans, f[i] + g[i]);
	printf("%lld\n",ans); 
	return 0;
}

B. 树上竞技

对每条边求贡献,设子树大小为\(s\),该边贡献为

\(\large \sum_{i = 1}^{m - 1}(^s_i)(^{n-s}_{m- i})min(i,m-i)\)

拆开\(min\),设\(k = (m - 1) / 2\)

\(\large f_s = \sum_{i = 1} ^ {k}(^s_i)(^{n - s}_{m - i})i\)

那么当前贡献为\(\large f_s + f_{n - s} + [m \%2 == 0](^s_{m/2})(^{n - s}_{m/2})m/2\)

组合数可以搞出来\(s g_s = f_s\)

\(\large g_s = \sum_{i = 1}^{k}(^{s - 1}_{i - 1})(^{n - s}_{m - i})\)

上项和一直为\(n - 1\),下面和为\(m - 1\),可以有这样的组合意义

\(n - 1\)个物品选\(m - 1\)个,前\(s - 1\)个物品最多选\(k - 1\)

转移有
$\large g_s - g_{s + 1} =(^{s - 1}_{k - 1}) (^{n - s - 1} _{m - k - 1}) $

考虑减去不合法的方案,在前\(s-1\)位置中选了\(k-1\)个,并且选了\(s\)位置的方案是不合法的,减去得到当前方案

式子挺玄乎的

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<iostream>
#include<set>
#include<map>

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = 1000005;
const int mod = 1000000007;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9'){c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
struct edge{int to, net;}e[maxn << 1 | 1];
int head[maxn], tot;
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
}
int fac[maxn], inv[maxn];
int c(int n, int m){if(n < m || n < 0 || m < 0)return 0;else return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;}
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int n, m, size[maxn], cs[maxn];
ll ans;
void dfs(int x, int fa){
	size[x] = 1;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa)continue;
		dfs(v, x);
		size[x] += size[v];
	}
	++cs[size[x]];
}
int g[maxn];
int main(){
	freopen("meeting.in","r",stdin);
	freopen("meeting.out","w",stdout);
	n = read(), m = read();
	fac[0] = 1; for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[n] = qpow(fac[n], mod - 2); for(int i = n - 1; i; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod; inv[0] = 1; 
	for(int i = 2; i <= n; ++i){int u = read(); add(u, i); add(i, u);}
	dfs(1, 0);
	int k = (m - 1) / 2;
	if(k)g[1] = c(n - 1, m - 1);
	for(int s = 1; s <= n; ++s)g[s + 1] = (g[s] - 1ll * c(s - 1 , k - 1) * c(n - s - 1, m - k - 1) % mod + mod) % mod;
	for(int s = 1; s <= n; ++s)g[s] = 1ll * g[s] * s % mod;
	for(int s = 1; s <= n; ++s)if(cs[s]){
		ll now = (g[n - s] + g[s]) % mod;
		if(m % 2 == 0)now = (now + c(s, m / 2) * 1ll * c(n - s, m / 2) % mod * (m / 2) % mod) % mod;
		ans = (ans + now * cs[s] % mod) % mod;
	}
	printf("%lld\n",ans);
	return 0;
}

C. 虚构推理

正解二分,用滑动窗口类似的方式找交集,(我觉得有点扫描线思想)

懒得打了,可以暴力枚举时间,每次\(lower\_bound\)找最远 的针即可

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<iostream>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = 100005;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9'){c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
double sz[maxn], fz[maxn];
int n;
int main(){ 
	freopen("unreal.in","r",stdin);
	freopen("unreal.out","w",stdout);
	n = read();
	int cnt = 0;
	for(int i = 1; i <= n; ++i){
		double h = read(), m = read(), s = read();
		if(h >= 12)h -= 12;
		fz[++cnt] = m * 6 + s / 10; 
		sz[cnt] = h * 30 + m / 2 + s / 120;
		fz[++cnt] = m * 6 + s / 10 + 360; 
		sz[cnt] = h * 30 + m / 2 + s / 120 + 360;
	}
	sort(fz + 1, fz + cnt + 1);
	sort(sz + 1, sz + cnt + 1);
	double ans = 0x3f3f3f3f3f;
	fz[0] = sz[0] = -0x3f3f3f3f3f; fz[cnt + 1] = sz[cnt + 1] = 0x3f3f3f3f3f;
	for(double h = 0; h < 360; h += 30){
		for(double mi = 0; mi < 360; mi += 0.001){
			double hu = h + mi / 12; if(hu < 180) hu = hu + 360;
			int p = lower_bound(sz + 1, sz + cnt + 1, hu - 180) - sz;
			int q = lower_bound(sz + 1, sz + cnt + 1, hu + 180) - sz - 1;
			double nans = max(sz[q] - hu, hu - sz[p]);
			hu = mi; if(hu < 180) hu += 360;
			p = lower_bound(fz + 1, fz + cnt + 1, hu - 180) - fz;
			q = lower_bound(fz + 1, fz + cnt + 1, hu + 180) - fz - 1;
			nans = max(nans, max(fz[q] - hu, hu - fz[p]));
			ans = min(ans, nans);
		}
	}
	printf("%lf\n",ans);
	return 0;
}

D. 记忆碎片

褐的\(Delov\)大佬的题解,讲的确实很好(我写题解好像会引流啊QAQ)

\(dp_{i,s}\)表示添加了\(i\)条树边,状态为\(s\)的方案数

两个状态不同,当且仅当存在某个大小的联通块数量不同,我们只关心某个大小的联通块有多少

考虑加入一条树边,我们把两个联通块并成一个,设状态中两个联通块分别有\(cnt_a, cnt_b\)个,大小为\(a ,b\)

那么他的贡献就是\(dp_{i - 1}{las} * cnt_a * cnt_b * a * b\)

特判\(a == b\)(会取重)

考虑加入非树边

非树边不会改变联通性

把联通块扫一遍,可以得到一共有多少条联通块内的边

减去已经加上的边的数量,就是多少位置可以加边,设其为 \(sum\),

那么对于第一条非树边,它有\(sum\)种选择方案,第二条有\(sum - 1\)种......这是\(sum^{\frac{cnt}{}}\)

现在剩下最后的问题是如何分状态,直接\(DFS\)拆分的话会炸,因为存在重复状态,所以我们写一个 \(hash\)函数,用\(map\)维护已经出现的状态的\(hash\)值对状态的映射,这样就能愉快的去重了

我这里使用了\(delov\)大佬的\(bfs\)写法,在预处理状态时顺便把转移边建了出来,方便后面操作,使用\(bitset\)貌似可以优化代码常数,但是实际上每次爆扫好像跑的更快?

“我以为statue是状态的英语,然而,,,,果然我还是太菜了,忽略奇怪的结构体名称”

upd : 本来以为没人注意,但是还是被发现了,该代码过不了样例2,因为样例2是非法的,然后在下降幂那里会传进去奇怪的参数,会导致re, 解决方法就是加点特判,实际上delov的下降幂传进了-1,也是特判过的

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<iostream>
#include<set>
#include<map>
#include<bitset>

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 42;
const ull base = 233;
const ull mod = 1e9 + 7;
const ll inv2 = 500000004;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9'){c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int fac[5005], inv[5005];
void pre(){
	int up = 1600;
	fac[0] = 1; for(int i = 1; i <= up; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[up] = qpow(fac[up], mod - 2); for(int i = up - 1; i; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod; inv[0] = 1; 
}
int down(int n, int m){return 1ll * fac[n] * inv[n - m] % mod;}
int cnt, n;
struct statue{
	bitset<44> b;
	int rem[maxn];
	void del(int x, int val){rem[x] -= val; if(!rem[x])b[x] = 0;}
	void add(int x, int val){if(!rem[x] && val)b[x] = 1; rem[x] += val;}
	ull	get_hash(){
		ull ans = 0;
		for(int i = 1; i <= n; ++i)ans = (ans * base + rem[i]) % mod;
		return ans; 
	}
}zt[50005];
int v[maxn];
map<ull, int>mp;
queue<int>q;
int head[45535], tot;
struct edge{int to, net, sizex, sizey;}e[4000005];
void add(int u, int v, int sx, int sy){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
	e[tot].sizex = sx;
	e[tot].sizey = sy;
}
void get_trans(){
	cnt = 1;
	zt[1].add(1, n);
	q.push(1); 
	mp[zt[1].get_hash()] = 1;
	while(!q.empty()){
		int x = q.front(); q.pop();
		statue nzt = zt[x];
		for(int now = zt[x].b._Find_first(); now <= n && zt[x].b[now]; now = zt[x].b._Find_next(now)){
			if(zt[x].rem[now] > 1){
				nzt.del(now, 2);
				nzt.add(now + now, 1);
				if(!mp[nzt.get_hash()]){zt[++cnt] = nzt; q.push(cnt); mp[nzt.get_hash()] = cnt;}
				add(x, mp[nzt.get_hash()], now, now);
				nzt.del(now + now, 1); nzt.add(now, 2);
			}
			nzt.del(now, 1);
			for(int nxt = zt[x].b._Find_next(now); nxt <= n && zt[x].b[nxt]; nxt = zt[x].b._Find_next(nxt)){
				nzt.del(nxt, 1); nzt.add(now + nxt, 1);
				if(!mp[nzt.get_hash()]){zt[++cnt] = nzt; q.push(cnt); mp[nzt.get_hash()] = cnt;}
				add(x, mp[nzt.get_hash()], now, nxt);
				nzt.add(nxt, 1); nzt.del(now + nxt, 1);
			}
			nzt.add(now, 1);
		}
	}
}
int dp[43][40005];

int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n = read(); pre();
	for(int i = 1; i < n; ++i)v[i] = read();
	sort(v + 1, v + n);
	get_trans();
	dp[0][1] = 1;
	for(int now = 1; now < n; ++now){
		if(now > 1)
			for(int i = 1; i <= cnt; ++i)if(dp[now - 1][i]){
				int sum = 0;
				for(int x = zt[i].b._Find_first(); x <= n && zt[i].b[x]; x = zt[i].b._Find_next(x))
					sum = (sum + 1ll * x * (x - 1) / 2 * zt[i].rem[x] % mod) % mod;
				sum = (sum - v[now - 1] + mod) % mod;
				dp[now - 1][i] = 1ll * dp[now - 1][i] * down(sum, v[now] - v[now - 1] - 1) % mod;

			}
		for(int i = 1; i <= cnt; ++i)if(dp[now - 1][i]){
			for(int j = head[i]; j; j = e[j].net){
				int v = e[j].to;
				int sx = e[j].sizex, sy = e[j].sizey;
				if(sx != sy)dp[now][v] = (dp[now][v] + 1ll * dp[now - 1][i] * zt[i].rem[sx] % mod * zt[i].rem[sy] % mod * sx % mod * sy % mod) % mod;
				else dp[now][v] = (dp[now][v] + 1ll * dp[now - 1][i] * zt[i].rem[sx] % mod * (zt[i].rem[sx] - 1) % mod * inv2 % mod * sx % mod * sx % mod) % mod;
			}
		}
	}
	int end; for(int i = 1; i <= cnt; ++i)if(zt[i].b[n]){end = i; break;}
	int res = n * (n - 1) / 2 - v[n - 1];
	int ans = 1ll * dp[n - 1][end] * fac[res] % mod;
	printf("%d\n",ans);
	return 0;
}
posted @ 2022-08-19 06:55  Chen_jr  阅读(250)  评论(24编辑  收藏  举报