1. Bertown roads
链接 https://codeforces.com/problemset/problem/118/E
tag
dfs树
题意
n ( 1 < = n < = 1 e 5 ) n(1<=n<=1e5) n(1<=n<=1e5)个点, m ( 1 < = m < = 1 e 5 ) m(1<=m<=1e5) m(1<=m<=1e5)条双向边,问能否让每条边变得单向使得图是一个强连通分量。如果能则输出方案。
分析
考虑dfs树,如果存在桥就是存在点没有被返祖边覆盖,根据定义,不妨记桥两边的点为 u , v u,v u,v, 如果u能到v,那么v就不存在别的路径到u,显然不符合题意的要求,否则每个点都至少被一条返祖边覆盖,我们可以构造令dfs树的树边往下连,而返祖边往上连即可满足要求。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;
int n,m,k;
vector<tuple<int,int,int>> g[N];
vector<pair<int,int>> t;
int f[N];
int dfn[N],low[N],tot;
bool dfs(int u,int fa)
{
dfn[u]=low[u]=++tot;
// cout<<u<<" "<<fa<<"\n";
for(auto [j,x,c]:g[u])
{
if(j==fa) continue;
if(!dfn[j])
{
if(!dfs(j,u)) return false;
if(!f[x]) f[x]=c;
low[u]=min(low[u],low[j]);
if(low[j]>dfn[u]) return false; // 存在桥
} else
{
low[u]=min(low[u],dfn[j]);
if(!f[x]) f[x]=c;
}
}
return true;
}
void slove()
{
cin>>n>>m;
rep(i,0,m)
{
int a,b; cin>>a>>b;
g[a].emplace_back(b,i,1);
g[b].emplace_back(a,i,-1);
t.emplace_back(a,b);
}
// dfs(1,0);
// cout<<"head\n";
// rep(i,1,n+1) cout<<dfn[i]<<" "<<low[i]<<"\n";
// return ;
if(!dfs(1,0)) cout<<"0\n";
else
{
rep(i,0,m)
{
auto [a,b]=t[i];
if(f[i]<0) swap(a,b);
cout<<a<<' '<<b<<"\n";
}
}
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
2. 数码
链接 https://ac.nowcoder.com/acm/problem/13221
tag
整数分块
题意
给定两个整数 l 和 r ,对于所有满足$1 ≤ l ≤ x ≤ r ≤ 10^9 $的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
分析
枚举每个约数的贡献,则答案为 ∑ i = 1 n i ∗ n i \sum_{i=1}^n i * \dfrac{n}{i} ∑i=1ni∗in,但n很多显然直接枚举不行,注意到右边是经典的整数分块形式,我们考虑枚举右边,则对于特定的数码x,一块区间 [ l , r ] [l,r] [l,r]的贡献是区间 [ x , x + 1 ) , [ x ∗ 10 , ( x + 1 ) ∗ 10 ) , x [ ∗ 100 , ( x + 1 ) ∗ 100 − 1 ) . . . [x,x+1), [x*10,(x+1)*10), x[*100,(x+1)*100-1)... [x,x+1),[x∗10,(x+1)∗10),x[∗100,(x+1)∗100−1)...与 [ l , r ] [l,r] [l,r]的区间交。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;
int n,m,k;
ll ans[N];
ll get(int x,int up)
{
ll res=0;
for(int l=1,r;l<=up;l=r+1)
{
r=up/(up/l);
ll s=0;
for(ll a=x,b=x+1;a<=up;a*=10,b*=10)
{
s+=max<ll>(0,min<ll>(b-1,r)-max<ll>(a,l)+1);
}
res+=s*(up/r);
}
return res;
}
void slove()
{
int l,r;
cin>>l>>r;
rep(i,1,10)
{
// cout<<get(i,r)<<" "<<get(i,l-1)<<"\n";
cout<<(get(i,r)-get(i,l-1))<<"\n";
}
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
3. Paint Box
链接 https://ac.nowcoder.com/acm/problem/13884
tag
组合数学,容斥定理
题意
n个盒子,从m种颜色中选出k进行染色,要求相邻的不能相同颜色,问染色结果恰有k种颜色的方案数。 ( 1 ≤ n , m ≤ 1 0 9 1 ≤ k ≤ 1 0 6 , k ≤ n , m ) (1≤n,m≤10^9 1≤k≤10^6, k≤n,\ m) (1≤n,m≤1091≤k≤106,k≤n, m)
分析
从m种颜色中选择
C
(
m
,
k
)
C(m,k)
C(m,k),然后进行染色的方案数
k
∗
(
k
−
1
)
n
−
1
k*(k-1)^{n-1}
k∗(k−1)n−1,但是这样表示的是<=k种颜色的方案数且有重复的部分,我们考虑二项式反演,令
x
n
x_n
xn为<=n种颜色的方案数,
y
n
y_n
yn为恰好n种颜色的方案数,则
x
n
=
k
∗
(
k
−
1
)
n
−
1
=
∑
i
=
1
n
C
(
n
,
i
)
∗
y
i
x_n=k*(k-1)^{n-1}=\sum_{i=1}^n C(n,i)*y_i
xn=k∗(k−1)n−1=∑i=1nC(n,i)∗yi,由二项式反演得到,
y
n
=
∑
i
=
1
n
(
−
1
)
n
−
i
∗
C
(
n
,
i
)
∗
x
i
y_n=\sum_{i=1}^n (-1)^{n-i}*C(n,i)*x_i
yn=∑i=1n(−1)n−i∗C(n,i)∗xi,
y
k
y_k
yk即为所求
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;
int n,m,k;
int add(int x,int y) {
x+=y;
return x<0?x+mod:x>=mod?x-mod:x;
}
int mul(int x,int y) {
return 1ll*x*y%mod;
}
struct Bio {
int fac[N],inv[N];
int _n;
int ksm(int a,int b,int res=1) {
for(;b;b>>=1,a=1ll*a*a%mod)
if(b&1) res=1ll*res*a%mod;
return res;
}
Bio(int n=N-1) : _n(n) {
fac[0]=inv[0]=1;
for(int i=1;i<=_n;++i) fac[i]=fac[i-1]*1ll*i%mod;
inv[_n]=ksm(fac[_n],mod-2);
for(int i=_n-1;i;--i) inv[i]=inv[i+1]*1ll*(i+1)%mod;
}
int C(int a,int b) {
if(a<b||b<0) return 0;
return 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;
}
} b;
void slove()
{
cin>>n>>m>>k;
int res=0;
for(int i=k,p=1;i;--i,p*=-1)
{
int t=mul(b.C(k,i), mul(i, b.ksm(i-1, n-1)));
res=add(res, mul(p, t));
}
for(int i=1,j=m;i<=k;++i,--j)
res=mul(res, mul(j, b.ksm(i, mod-2)));
cout<<res<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
4. Vasya and Good Sequences
链接 https://codeforces.com/problemset/problem/1030/E
tag
dp
题意
给定一个数组,每次操作可以将一个数的某一位 1 1 1 移动到这个数的别的位上,你可以选择一个区间对于区间中的数可以使用任意多次操作,如果操作结束后这个区间的异或和为0,则为好区间,问有多少 ( 1 ≤ n ≤ 3 ∗ 1 0 5 ) (1≤n≤3*10^5) (1≤n≤3∗105) ( 1 ≤ a i ≤ 1 0 18 ) (1≤ai≤10^{18}) (1≤ai≤1018)
分析
如果这一段异或和为0那么每一个1都要有另一个1都会抵消,所以这一段的1的数量要偶数,然后手玩一下发现如果某个值大于这段别的1的数量之和也不行,然后考虑维护这个结论,前者可以通过开辟数组记忆化实现,而后者因为 ( 1 ≤ a i ≤ 1 0 18 ) (1≤ai≤10^{18}) (1≤ai≤1018) 所以对于某个数作为最大值,这些有矛盾的区间长度<=65,我们暴力枚举这个长度。
参考代码
#include<bits/stdc++.h>
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=19,mod=1e9+7;
const int N=1<<bit|7;
int n,m,k;
ll c[N],a[N];
int cnt[N][2];
void slove()
{
cin>>n;
rep(i,1,n+1)
{
cin>>a[i];
c[i]=__builtin_popcountll(a[i]);
}
ll res=0,p=0;
cnt[0][0]=1;
for(int i=1;i<=n;++i)
{
ll s=c[i],mx=c[i];
p^=s&1;
for(int j=i-1;j>max(i-65,0);--j) // 处理最大值
{
s+=c[j],mx=max(mx,c[j]);
if(s%2==0&&mx<=s-mx) res++;
}
if(i>=66) res+=cnt[i-66][p];
rep(j,0,2) cnt[i][j]=cnt[i-1][j];
cnt[i][p]++;
// cout<<i<<" "<<c[i]<<" "<<res<<"\n";
}
cout<<res<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
5. Moovie Mooving G
链接 https://www.luogu.com.cn/problem/P3118
tag
状态压缩
题意
给一个时间T,和n部电影,每部电影会有一个固定的持续时间和多个开始时间,现你要从时间0开始连续看电影,不能看同一部电影,问最少要看多少部电影才能到时间T。 1 < = T < = 1 0 9 , 1 < = n < = 20 1<=T<=10^9 ,\ 1<=n<=20 1<=T<=109, 1<=n<=20
分析
状态压缩维护最大值,而对于要枚举的电影,如果暴力枚举决策,会超时,贪心考虑应当选择最大的小于当前度过的时间,如果 t i , j + d [ i ] < = n o w t_{i,j}+d[i]<=now ti,j+d[i]<=now显然选择后不优,我们用 s t d : : s e t std::set std::set维护有序序列查找所需的值。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;
int n,m,k;
int a[N];
set<int> s[N];
ll f[N];
void slove()
{
cin>>n>>k;
rep(i,0,n)
{
cin>>a[i];
cin>>m;
while(m--)
{
int val; cin>>val;
s[i].emplace(val);
}
}
for(int i=1;i<1<<n;++i)
{
for(int j=0;j<n;++j)
{
if(i>>j&1)
{
ll t=f[i^(1<<j)];
auto it=s[j].upper_bound(t);
if(it==s[j].begin()) continue;
// cout<<i<<" "<<(*it)<<' '<<t<<' '<<j<<"\n";
it--;
// cout<<(*it)<<"\n";
t=a[j]+*it;
f[i]=max(f[i],t);
}
}
}
int res=n+1;
rep(i,0,1<<n)
if(f[i]>=k) res=min(res,__builtin_popcount(i));
if(res==n+1) res=-1;
cout<<res<<'\n';
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
6. 二分图染色
链接 https://ac.nowcoder.com/acm/problem/13229
tag
dp、容斥
题意
给定一个完全二分图,图的左右两边的顶点数目相同。现要给图中的每条边染成红色、蓝色、或者绿色,并使得任意两条红边不共享端点、同时任意两条蓝边也不共享端点。
计算所有满足条件的染色的方案数,并对
1
0
9
+
7
10^9+7
109+7取模。
1
<
=
n
<
=
1
0
7
1<=n<=10^7
1<=n<=107
分析
首先考虑只有红色和绿色,令 f [ n ] f[n] f[n]为点数为n的方案数,我们可以看成一个nn的矩阵一开始都是绿色且一行或者一列最多一个红色,由n-1向n转移,如果不填红色f[n]=f[n-1],否则若只有一个红色,有2n-1个格子选择,f[n]=f[n-1](2n-1),但是这样会有重复,重复的部分为从(n-1)*(n-1)*f[n-2]。然后考虑蓝色,如果没有两者不会占据同一个格子,根据乘法原理为f[n]*f[n]但有重叠矛盾的,考虑容斥去重,对于枚举哪些行有矛盾,则这些只能选一种颜色,即 A ( n , i ) A(n,i) A(n,i) 则答案为 ∑ i = 0 n ( − 1 ) i C ( n , i ) ∗ A ( n , i ) ∗ f [ n − i ] 2 \sum_{i=0}^{n} (-1)^{i} C(n, i)*A(n,i) *f[n-i]^2 ∑i=0n(−1)iC(n,i)∗A(n,i)∗f[n−i]2
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1e7+10;
int n,m,k;
int add(int x,int y) {
x+=y;
return (x%mod+mod)%mod;
}
int mul(int x,int y) {
return 1ll*x*y%mod;
}
struct Bio {
int fac[N],inv[N];
int _n;
int ksm(int a,int b,int res=1) {
for(;b;b>>=1,a=1ll*a*a%mod)
if(b&1) res=1ll*res*a%mod;
return res;
}
Bio(int n=N-1) : _n(n) {
fac[0]=inv[0]=1;
for(int i=1;i<=_n;++i) fac[i]=fac[i-1]*1ll*i%mod;
inv[_n]=ksm(fac[_n],mod-2);
for(int i=_n-1;i;--i) inv[i]=inv[i+1]*1ll*(i+1)%mod;
}
int C(int a,int b) {
if(a<b||b<0) return 0;
return 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;
}
int A(int a,int b) {
if(a<b||b<0) return 0;
return 1ll*fac[a]*inv[a-b]%mod;
}
} b;
int f[N];
void slove()
{
cin>>n;
f[0]=1,f[1]=2;
rep(i,2,n+1)
f[i]=add(mul(2*i, f[i-1]), -mul(mul(i-1, i-1), f[i-2]));
int res=0;
for(int i=0,p=1;i<=n;++i,p*=-1) // 容斥
{
int t=mul(b.C(n,i), mul(b.A(n,i), mul(f[n-i], f[n-i])));
res=add(res, mul(p, t));
}
cout<<res<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
7. PUS
链接 https://www.luogu.com.cn/problem/P3588
tag
线段树,拓扑排序,贪心
题意
给定一个长度为
n
n
n
(
1
<
=
n
<
=
1
0
5
)
(1<=n<=10^5)
(1<=n<=105)的正整数序列 ,每个数都在
1
1
1 到
1
0
9
10^9
109 范围内,告诉你其中
s
s
s
(
1
<
=
s
<
=
1
0
5
)
(1<=s<=10^5)
(1<=s<=105)个数,并给出
m
(
1
<
=
m
<
=
2
∗
1
0
5
)
m\ (1<=m<=2*10^5)
m (1<=m<=2∗105) 条信息,每条信息包含三个数
l
r
k
l\ r\ k
l r k以及接下来
k
k
k个正整数,表示
a
l
a
l
+
1
.
.
.
a
r
a_l\ a_{l+1}\ ... a_r
al al+1 ...ar里这
k
k
k个数中的任意一个都比任意一个剩下的
r
−
l
−
k
+
1
r-l-k+1
r−l−k+1个数大(严格大于,即没有等号)。
请任意构造出一组满足条件的方案,或者判断无解。
分析
差分约束问题,因为边权都是正的或者都是负的,我们可以拓扑排序实现,每个数都有上下界,可以用拓扑排序维护最小值或者维护最大值,我们这里实现的是初始化最大值,同时过程中维护最大值。分析时间复杂度,如果暴力建图,那么边数是 m 3 m^3 m3,我们考虑虚拟源点优化,对于一组限制,将这k个点向虚拟源点连一条边权为1的边,同时虚拟源点对剩下的区间连一条边权为0的边,因为是区间连边,所以可以考虑线段树优化建图,最后在跑拓扑排序的时候,注意细节特判。
参考代码
#include<bits/stdc++.h>
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;
int n,m,k;
vector<pair<int,int>> g[N];
int lef[N],in[N],tot;
bool st[N];
ll dis[N];
void build(int u,int l,int r)
{
tot=max(tot,u);
if(l==r) return lef[l]=u,void();
g[u].emplace_back(ls,0);
g[u].emplace_back(rs,0);
in[ls]++,in[rs]++;
build(ls,l,mid),build(rs,mid+1,r);
}
void modify(int u,int l,int r,int ql,int qr,int v,int w)
{
if(ql<=l&&qr>=r)
{
g[v].emplace_back(u,w);
in[u]++;
return ;
}
if(ql<=mid) modify(ls,l,mid,ql,qr,v,w);
if(qr>mid) modify(rs,mid+1,r,ql,qr,v,w);
}
bool top()
{
queue<int> q;
int cnt=0;
rep(i,1,tot+1)
{
if(!st[i]) dis[i]=1e9;
if(!in[i])
q.emplace(i);
}
while(q.size())
{
int t=q.front();
q.pop();
if(dis[t]<1) return false;
cnt++;
for(auto [j,w]:g[t])
{
ll to=dis[t]+w;
if(!st[j]) dis[j]=min(dis[j],to);
else if(dis[j]>to) return false;
if(--in[j]==0) q.emplace(j);
}
}
return cnt==tot;
}
void slove()
{
int s;
cin>>n>>s>>m;
build(1,1,n);
while(s--)
{
int id,val; cin>>id>>val;
id=lef[id];
st[id]=true;
dis[id]=val;
}
while(m--)
{
int l,r,k;
cin>>l>>r>>k;
tot++; // 建立虚拟源点
int p=l;
while(k--)
{
int x; cin>>x;
g[lef[x]].emplace_back(tot,-1);
in[tot]++;
if(p<=x-1) modify(1,1,n,p,x-1,tot,0);
p=x+1;
}
if(p<=r) modify(1,1,n,p,r,tot,0);
}
// cout<<tot<<"\n";
if(!top()) cout<<"NIE\n";
else {
cout<<"TAK\n";
rep(i,1,n+1) cout<<dis[lef[i]]<<" \n"[i==n];
}
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
8. 树上游戏
链接 https://www.luogu.com.cn/problem/P2664
tag
dsu
题意
给你一棵树,每个点都一种颜色,定义 s ( i , j ) s(i,\ j) s(i, j)为i到j路径上颜色种类的数量,现求 s u m i = ∑ j = 1 n s ( i , j ) sum_i = \sum_{j=1}^{n} s(i,\ j) sumi=∑j=1ns(i, j)
分析
首先对于只有一种颜色分析,我们将为当前颜色的点除去,这样树变成了多个连通块,对于每个连通块的点的贡献就是n-它所属的连通块的大小,对于多种颜色,如果暴力枚举显然不行,考虑到只有n个点,我们处理每个点对子树的贡献,可以用树上差分维护,同时开辟一个数组维护每种颜色的极大连通块,因为是树上差分操作,我们再维护他的dfs序来查询子树中同种颜色的位置,最后特殊处理根节点把所有颜色的贡献加到根节点上。
参考代码
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20;
const int N=1<<bit|7;
int n,m,k;
vector<int> g[N];
ll a[N],sz[N],cs[N],f[N]; // cs数组维护该子树的极大连通块
int dfn[N],cnt;
vector<int> w[N];
void dfs(int u,int fa)
{
sz[u]=1;
dfn[u]=++cnt;
for(auto j:g[u])
{
if(j==fa) continue;
ll t=cs[a[u]]; // 记录原先的贡献
dfs(j,u);
sz[u]+=sz[j];
ll s=sz[j]-(cs[a[u]]-t); // 当前节点的贡献是当前子树大小-新增的贡献
f[j]+=s;
cs[a[u]]+=s;
while(w[a[u]].size()&&dfn[w[a[u]].back()]>dfn[u]) // 处理子树
f[w[a[u]].back()]-=s,w[a[u]].pop_back();
}
cs[a[u]]++;
w[a[u]].emplace_back(u);
}
void dfs2(int u,int fa)
{
f[u]+=f[fa];
for(auto j:g[u])
if(j!=fa) dfs2(j,u);
}
void slove()
{
cin>>n;
map<int,int> h;
rep(i,1,n+1) cin>>a[i],h[a[i]]=1;
rep(i,1,n)
{
int a,b; cin>>a>>b;
g[a].emplace_back(b);
g[b].emplace_back(a);
}
dfs(1,0);
for(auto [sa,_]:h) // 特殊处理根节点使得包含所有颜色的贡献
{
f[1]+=n-cs[sa];
for(auto u:w[sa])
f[u]-=n-cs[sa];
}
dfs2(1,0);
m=h.size();
rep(i,1,n+1) cout<<1ll*n*m-f[i]<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
cout<<fixed<<setprecision(8);
int _=1;
// cin>>_;
while(_-->0) slove();
return 0;
}
9. Accumulation Degree
链接 https://ac.nowcoder.com/acm/problem/51180
tag
换根dp
题意
给你一棵树,你可以选择一个点作为根,然后流量从根出发,流到每片叶子,路径上有最大流量限制,结果为所有叶子最后得到的流量之和,问你最多能得到多少流量。 2 < = n < = 1 0 5 2<=n<=10^5 2<=n<=105
分析
显然答案与根有关,所以会有一个换根dp,我们首先分析对与一个有根树怎么做,如果从根出发自上而下, 那么某个点分配流量的时候会出现问题,又因为流量或者说边权比较大,所以强行dp是不行的,我们从叶子出发,对于某个叶子的父亲贪心的做给他的流量就是他到叶子的边权,而对于这个点的父亲,流量又限制于它到父亲的边权,我们要对两者取最小值,这样我们得到dp的方程,设当前节点是u,儿子是v,边权是w,如果v是叶子,则
f
[
u
]
+
=
w
f[u]+=w
f[u]+=w否则
f
[
u
]
+
=
m
i
n
(
f
[
v
]
,
w
)
f[u]+=min(f[v],w)
f[u]+=min(f[v],w),然后考虑换根,我们把某个子树的贡献消去,并将当前子树的贡献加到另一颗子树的贡献也是和上面一样的方程。具体可以参考代码。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=200010;
int n,m,k;
vector<pair<int,ll>> g[N];
ll f[N],res;
bool dfs(int u,int fa)
{
f[u]=0;
if(g[u].size()==1&&fa) return true; // 判断是否是叶子
for(auto [j,w]:g[u])
{
if(j==fa) continue;
if(dfs(j,u)) f[u]+=w;
else f[u]+=min<ll>(f[j],w);
}
return false;
}
void dfs1(int u,int fa)
{
res=max(f[u],res);
for(auto [j,w]:g[u])
{
if(j==fa) continue;
if(g[j].size()==1) f[u]-=w; // 如果j是叶子
else f[u]-=min<ll>(f[j],w);
ll t;
if(g[u].size()==1) t=w; // 如果 u 是叶子
else t=min<ll>(f[u],w);
f[j]+=t;
dfs1(j,u);
f[j]-=t;
if(g[j].size()==1) f[u]+=w;
else f[u]+=min<ll>(f[j],w);
}
}
void slove()
{
cin>>n;
assert(n>1);
rep(i,1,n+1) g[i].clear();
res=0;
rep(i,1,n)
{
int a,b,c; cin>>a>>b>>c;
g[a].emplace_back(b,c);
g[b].emplace_back(a,c);
}
dfs(1,0);
dfs1(1,0);
cout<<res<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
10. Unfair Nim
链接 https://atcoder.jp/contests/abc172/tasks
tag
博弈,分类讨论
题意
给你一个长度为 n n n的数组 2 < = n < = 300 2<=n<=300 2<=n<=300,两人进行nim游戏,游戏开始前第一个人可以将第一堆的部分石子移动到第二堆,但不能全部移完,问最少移动多少石子使得第一个人必胜,如不能则输出 − 1 -1 −1;
分析
考虑经典nim游戏,必胜为异或和不为0,则设移动后的第一堆的数量为x,第二堆的数量为y,后面的异或和为k,则
x
+
y
=
a
[
1
]
+
a
[
2
]
x+y=a[1]\ +\ a[2]
x+y=a[1] + a[2] 且
x
⨁
y
=
k
x\bigoplus y=k
x⨁y=k,一个加法一个异或,我们发现难以分析,考虑公式
a
+
b
=
(
a
⨁
b
)
+
2
∗
(
a
⨂
b
)
a+b = (a\bigoplus b) + 2*(a \bigotimes b)
a+b=(a⨁b)+2∗(a⨂b),同时令
(
u
=
a
[
1
]
+
a
[
2
]
−
k
)
/
2
(u=a[1]+a[2]-k)/2
(u=a[1]+a[2]−k)/2,则问题等价于
x
⨂
y
=
u
,
x
⨁
=
k
x\bigotimes y = u,\ x\bigoplus =k
x⨂y=u, x⨁=k找到尽量大的x,这样我们可以讨论每一位来处理问题。
首先处理变形中的问题,如果u<k或者u-k不能被2整除就是无解。
基于与运算则u有的位x也要有,初始化x=u; 然后从高位开始分析,
如果某一位u,k都有那么显然无解,如果都无,则我们也不能选这一位。
如果u有,k无,那么这一位两个都要有1;
如果u无,k有,那么这一位只能有一个有1,我们基于贪心如果加上这一位有x<=a[1],那就加上否则不加。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;
int n,m,k;
ll a[N];
void slove()
{
cin>>n;
ll y=0;
rep(i,0,n)
{
cin>>a[i];
if(i>1) y^=a[i];
}
ll x=a[0]+a[1];
bool nic=true;
ll res=0;
if(x<y) nic=false;
else
{
if((x-y)%2) nic=false;
x=(x-y)/2;
res=x;
for(int i=60;~i;--i)
{
int sx=x>>i&1,sy=y>>i&1;
if(sx&&sy) nic=false;
else if(!sx&&!sy) ;
else if(sx&&!sy) ;
else
{
if((res|(1ll<<i))<=a[0]) res|=1ll<<i;
}
}
nic&=!!res; // 处理初始化边界问题
nic&=res<=a[0];
}
if(nic) cout<<a[0]-res<<"\n";
else cout<<"-1\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}