链接:https://ac.nowcoder.com/acm/contest/296
C
给定一棵有根树,边有边权,两个人轮流操作,每次可以把一条边的权值减少一,权值变为 0 表示把子树砍掉,问先手第一轮操作哪些边使得自己必胜。 n ≤ 1 0 6 n\leq 10^6 n≤106
之前的博客,发现这道题本质上是图的删边游戏。把环缩掉,就变成了树上删边游戏了。结论是:叶子的 sg 为 0,非叶子的 sg 为所有叶子的 sg+1 的异或。求方案就是求删掉哪些边使得根的 sg 为 0。dfs 一遍即可。
E
有 n 个集合成一个环,每个集合大小为 m,要求 ∣ S i − S j ∣ ≥ l i |S_i-S_j|\geq l_i ∣Si−Sj∣≥li,要求最小化 ∣ ⋃ S i ∣ |\bigcup S_i| ∣⋃Si∣。
首先,如果是一排,答案就是
max
(
m
+
l
i
)
\max (m+l_i)
max(m+li)。
如果是一个环,就要让最后一个集合和第一个集合的差尽可能大。我们二分一个答案,然后尝试 DP 这个东西。设
f
i
,
j
,
0
/
1
f_{i,j,0/1}
fi,j,0/1 表示第 i 个集合与第一个集合的交 最多/最少 是多少。如果我们想让 i 的交最多,那么 i-1 的交一定要尽量少,反之亦然。
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int N=1000010;
ll f[N][2],l[N];int n;
int read()
{
int x=0,flag=1;char c=getchar();
while(!isdigit(c)) {if(c=='-') flag=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*flag;
}
bool check(ll lim,ll m)
{
f[0][0]=f[0][1]=m;
ll A=m,B=lim-m;
for(int i=1;i<n;i++)
{
ll ss=f[i-1][0],tt=m-ss;
if(A-ss>=l[i]) f[i][1]=m;
else f[i][1]=m-(l[i]-(A-ss));
ss=f[i-1][1],tt=m-ss;
if(B-tt>=l[i]) f[i][0]=0;
else f[i][0]=l[i]-(B-tt);
}
return m-f[n-1][0]>=l[0];
}
int main()
{
int T=read();
while(T--)
{
n=read(); ll m,mx=0; cin>>m;
for(int i=0;i<n;i++) cin>>l[i],mx=max(mx,l[i]);
if(mx>m) {cout<<-1<<'\n';continue;}
ll l=m,r=m+2*mx,ans=-1;
while(l<=r)
{
ll mid=l+r>>1;
if(check(mid,m)) r=mid-1,ans=mid;
else l=mid+1;
}
assert(ans!=-1);
cout<<ans<<'\n';
}
return 0;
}
G
有一个由 w 组成的无限长度的字符串 w w r w w r . . . ww^rww^r... wwrwwr..., w r w^r wr 表示把 w 翻转。从这个无限长的字符串里截一个子串 s(长度严格大于 w),满足 s 包含 w 或者 w 的反串作为子串。现在给出 s,求有多少子区间满足这个区间的字符串可能是 w。字符串长度小于等于1e6。
H
n 个点 m 条边的无向图,有 k 个加速点。你一开始速度为 1,经过一个加速点速度会乘2。可以经过一个点/边 多次,注意连续经过的两个加速点不能是同一个。求从 1 到 n 的最短时间。 k ≤ n ≤ 100 , m ≤ 8000 k\leq n\leq 100,m\leq 8000 k≤n≤100,m≤8000。
设 f i , j f_{i,j} fi,j 表示从 1 到 i 经过 j 个加速点的最短时间,枚举经过的最后一个加速点是什么,预处理两点最短路即可转移。