暑假集训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;
}