题目链接:https://codeforces.com/gym/104008
G Group Homework
题目大意:在树上选出两条链,使得在两条链选中的点中,只被一条链选中的点的点权和最大。
题解:显然两条链要么不相交,要么只相交于一个点。因为如果两条链相交的点多于一个,那一定可以通过交换链的端点的方法使得答案更优。这个结论可以参考出题人给出的示意图,如下图:
因此,问题转化为求两条不相交的链的点权和以及求以一个点为根的最大的四个儿子的深度和。我们很容易能够想到可以使用换根dp的方法来解决这两个问题,问题在于码量较大,细节较多,不是很容易实现。
具体讲一下dp的过程,可以先将1号点作为根,维护每个点的深度
h
x
h_x
hx和最长链的长度
l
x
l_x
lx,来完成将1看作根结点的求解:
h
x
=
m
a
x
{
h
s
o
n
}
+
a
x
l
x
=
m
a
x
{
l
s
o
n
,
h
s
o
n
1
+
h
s
o
n
2
+
a
x
}
h_x=max\{ h_{son}\}+a_x \\ l_x=max\{l_{son},h_{son1}+h_{son2}+a_x\}
hx=max{hson}+axlx=max{lson,hson1+hson2+ax}
其中,
h
s
o
n
1
h_{son1}
hson1和
h
s
o
n
2
h_{son2}
hson2分别表示
x
x
x的两个最大的儿子深度值。可以通过将
x
x
x的所有儿子的
h
h
h值排序的方法来得到。
然后再考虑如何换根,假设当前结点为
x
x
x,换根结点为
i
i
i,也就是说,在以
x
x
x为根的树中,
i
i
i结点是
x
x
x结点的儿子结点。
我们可以先将
x
x
x结点的所有儿子的
l
l
l和
h
h
h全部取出,然后从大到小排序,这样就可以通过将前四大的
h
h
h值加和来更新答案,也就是四个最大的儿子的深度,下面考虑两条不相交的链的情况。
首先考虑
x
x
x结点:换根之后深度
h
x
h_x
hx显然不能再从
i
i
i结点转移而来,而应该从其他儿子的最大值中转移得到,所以我们只需要判断
h
i
h_i
hi结点是否是最大的值即可,是最大值就从次大的
h
h
h值中转移,不是最大值就不需要更新。最长链
l
x
l_x
lx显然也不能从结点
i
i
i来更新,因此先判断
l
i
l_i
li和
l
l
l的最大值之间的关系,决定是否从次大的
l
l
l值中转移。然后还要判断
h
i
h_i
hi和前二大
h
h
h值的关系,因为需要用到除了
h
i
h_i
hi之外的前两大结点进行转移。
h
x
=
m
a
x
{
h
s
o
n
}
+
a
x
(
s
o
n
≠
i
)
l
x
=
m
a
x
{
l
s
o
n
,
h
s
o
n
1
+
h
s
o
n
2
+
a
x
}
(
s
o
n
,
s
o
n
1
,
s
o
n
2
≠
i
)
h_x=max\{ h_{son}\}+a_x ~~~(son\neq i)\\ l_x=max\{l_{son},h_{son1}+h_{son2}+a_x\}~~~(son,son1,son2\neq i)
hx=max{hson}+ax (son=i)lx=max{lson,hson1+hson2+ax} (son,son1,son2=i)
这时,我们求得了以
x
x
x为根且不含
i
i
i这个儿子的最长链长度和以
i
i
i为根且不包含
x
x
x这个儿子的最长链长度,因此这两条最长链一定不交,可以用来更新答案。
然后再考虑结点
i
i
i,相当于多了一个儿子
x
x
x来进行转移:
l
i
=
m
a
x
{
l
i
,
l
x
,
h
i
+
h
x
}
h
i
=
m
a
x
{
h
i
,
h
x
+
a
x
}
l_i=max\{ l_i,l_x,h_i+h_x \} \\ h_i=max\{h_i,h_x+a_x\}
li=max{li,lx,hi+hx}hi=max{hi,hx+ax}
这样,每次换完根之后再将原来的值还原回去,就可以实现换根dp求解了。
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#define time chrono::system_clock::now().time_since_epoch().count()
#include<ext/pb_ds/tree_policy.hpp>
#define clean(x) memset(x,0,sizeof(x))
#define fil(x,n) fill(x,x+1+n,0)
#define inf 2000000009
#define maxn 1000005
using namespace std;
using namespace __gnu_pbds;
mt19937_64 rnd(time);
cc_hash_table<int,int>mp;
int read() {
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') x=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
signed main()
{
int n=read();int ans=0;
vector<int>a(n+1),h(n+1),l(n+1);
vector<vector<int>>g(n+1);
function<void(int,int)>dfs1=[&](int x,int fa) {
h[x]=a[x];l[x]=a[x];
vector<int>son_h;
for(int i:g[x]) {
if(i==fa) continue;
dfs1(i,x);
h[x]=max(h[i]+a[x],h[x]);
son_h.push_back(h[i]);
}
son_h.push_back(0);
sort(son_h.rbegin(),son_h.rend());
for(int i:g[x]) {
if(i==fa) continue;
l[x]=max({l[x],l[i],son_h[0]+son_h[1]+a[x]});
}
};
function<void(int,int)>dfs2=[&](int x,int fa) {
vector<int>son_h,son_l; int res=0;
for(int i:g[x]) {
son_h.push_back(h[i]);
son_l.push_back(l[i]);
}
son_h.push_back(0);son_l.push_back(0);son_h.push_back(0);
sort(son_h.rbegin(),son_h.rend());
sort(son_l.rbegin(),son_l.rend());
for(int i=0;i<min(4,(int)son_h.size());i++) res+=son_h[i];
ans=max(ans,res);
for(int i:g[x]) {
if(i==fa) continue;
int pre_x,pre_i,pre_lx,pre_li;
pre_x=h[x];pre_i=h[i];pre_lx=l[x];pre_li=l[i];
l[x]=a[x];
if(l[i]==son_l[0]) l[x]=max(l[x],son_l[1]);
else l[x]=max(l[x],son_l[0]);
if(son_h[0]==h[i]) h[x]=son_h[1]+a[x];
else h[x]=son_h[0]+a[x];
if(son_h[0]==h[i]) l[x]=max(l[x],son_h[1]+son_h[2]+a[x]);
else if(son_h[1]==h[i]) l[x]=max(l[x],son_h[0]+son_h[2]+a[x]);
else l[x]=max(l[x],son_h[0]+son_h[1]+a[x]);
ans=max(ans,l[x]+l[i]);
l[i]=max({l[i],l[x],h[x]+h[i]});
h[i]=max(h[x]+a[i],h[i]);
dfs2(i,x);
h[x]=pre_x;h[i]=pre_i;
l[x]=pre_lx;l[i]=pre_li;
}
};
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++) {
int aa=read(),bb=read();
g[aa].push_back(bb);
g[bb].push_back(aa);
}
if(n==1) {
puts("0");
return 0;
}
dfs1(1,0);
dfs2(1,0);
cout<<ans<<endl;
return 0;
}
J. Permutation Puzzle
题目大意:构造出一个长度为 n n n排列,使得这个排列满足 m m m个形如 a u < a v a_u<a_v au<av的大小关系限制,并且初始有已经填入的数字。
题解:对于形如 a u < a v a_u<a_v au<av的关系,我们很容易联系到拓扑排序的性质,我们可以通过建立正向图来进行拓扑排序求解每个位置最小的可能数字 l i l_i li,通过建立反向图求解每个位置最大的可能数字 r i r_i ri,然后问题就转化为区间和点的覆盖问题,可以通过优先队列来实现,具体地,将区间按照左端点排序,然后从小到大枚举需要填的数字,每次加入比当前值小的区间,然后用优先队列维护右端点最小的区间作为这个数字应该填入的区间。
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#define time chrono::system_clock::now().time_since_epoch().count()
#include<ext/pb_ds/tree_policy.hpp>
#define inf 2000000009
#define maxn 1000005
#define int long long
using namespace std;
using namespace __gnu_pbds;
mt19937_64 rnd(time);
cc_hash_table<int,int>mp;
int read() {
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') x=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
void solve() {
int n=read(),m=read();int ok=1;
vector<int>a(n+1),l(n+1,-1),r(n+1,1e9),de1(n+1),de2(n+1);
vector<vector<int>>g1(n+1),g2(n+1);
for(int i=1;i<=n;i++) {
a[i]=read();
if(a[i]!=0) l[i]=r[i]=a[i];
}
for(int i=1;i<=m;i++) {
int aa=read(),bb=read(); //aa->bb;
g1[aa].push_back(bb);
g2[bb].push_back(aa);
de1[bb]++;de2[aa]++;
}
//g1:cal_L g2:cal_R
auto topo1=[&]() {
queue<int>q;
for(int i=1;i<=n;i++) {
if(de1[i]==0) {
q.push(i);
if(a[i]==0) l[i]=1;
}
}
while(q.size()) {
int u=q.front();q.pop();
for(int i:g1[u]) {
int res=l[u]+1;
if(res>n) {ok=0;return;};
if(a[i]!=0&&a[i]<res) {ok=0;return;}
else if(a[i]!=0) l[i]=a[i];
else l[i]=max(l[i],res);
de1[i]--;
if(de1[i]==0) q.push(i);
}
}
};
auto topo2=[&]() {
queue<int>q;
for(int i=1;i<=n;i++) {
if(de2[i]==0) {
q.push(i);
if(a[i]==0) r[i]=n;
}
}
while(q.size()) {
int u=q.front();q.pop();
for(int i:g2[u]) {
int res=r[u]-1;
if(res<1) {ok=0;return;};
if(a[i]!=0&&a[i]>res) {ok=0;return;}
else if(a[i]!=0) r[i]=a[i];
else r[i]=min(r[i],res);
de2[i]--;
if(de2[i]==0) q.push(i);
}
}
};
topo1();
topo2();
vector<array<int,3>>g(n+1);
vector<int>ans(n+1);
for(int i=1;i<=n;i++) {
g[i]={l[i],r[i],i};
if(r[i]<l[i]) ok=0;
}
sort(g.begin()+1,g.end());
priority_queue<pair<int,int>>q;
int lt=1;
for(int i=1;i<=n;i++) {
while(lt<=n) {
if(g[lt][0]==i) q.push({-g[lt][1],g[lt][2]});
else break;
lt++;
}
if(q.size()==0) {ok=0;break;}
auto [val,id]=q.top();q.pop();
if(i<=-val) ans[id]=i;
else ok=0;
}
if(!ok) {puts("-1");return;}
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
puts("");
}
signed main()
{
int t=read();
while(t--) solve();
return 0;
}
K. Barrel Theory
题目大意:构造长度为
n
n
n的正整数序列使得和为
m
m
m,并且序列的异或和要严格小于最小值。
题解:首先可以看出,
n
=
1
n=1
n=1时,一定无解。
然后,
n
=
2
n=2
n=2时,如果
m
+
1
2
\frac{m+1}2
2m+1和
m
2
\frac{m}2
2m不能满足条件,就一定无解,否则就可以这样构造。
对于
n
=
3
n=3
n=3的情况可以打表发现其中如果
m
>
19
m>19
m>19就一定有解,并且其中的某一个数字不会超过7,因此我们就可以对于
n
=
3
n=3
n=3的情况,暴力枚举所有可能即可。
对于
n
>
3
n>3
n>3的情况,可以分成以下四类:
-
n
n
n为偶数,
m
m
m为偶数:
构造(1,1,1,1……),(m-n+2)/2,(m-n+2)/2。 -
n
n
n为奇数,
m
m
m为偶数(需要满足
m
>
n
+
2
m>n+2
m>n+2):
构造(1,2,3),(1,1,1,1……),(m-n-1)/2,(m-n-1)/2 -
n
n
n为偶数,
m
m
m为奇数(需要满足
m
>
2
n
m>2n
m>2n):
构造(2,3),(2,2,2,2……),(m-2n+3)/2,(m-2n+3)/2 -
n
n
n为奇数,
m
m
m为奇数(需要满足
m
>
2
n
+
6
m>2n+6
m>2n+6):
构造(4,7,2),(2,2,2,2……),(m-2n-3)/2,(m-2n-3)/2
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#define time chrono::system_clock::now().time_since_epoch().count()
#include<ext/pb_ds/tree_policy.hpp>
#define clean(x) memset(x,0,sizeof(x))
#define fil(x,n) fill(x,x+1+n,0)
#define inf 2000000009
#define maxn 2000005
using namespace std;
using namespace __gnu_pbds;
mt19937_64 rnd(time);
cc_hash_table<int,int>mp;
int read() {
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') x=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
vector<int>ans;
int check(int n,int m) {
int x,y,pd=0;
if(n==2) { //n=2
x=(m+1)/2;
y=m/2;
if((x^y)<min(x,y)) pd=1;
ans.push_back(x);
ans.push_back(y);
}
else if(n==3) { //n=3
for(int x=1;x<=m/3;x++) {
for(int y=x;y<=(m-x)/2;y++) {
int z=m-x-y;
if((x^y^z)<min({x,y,z})) {
pd=1;
ans.push_back(x);
ans.push_back(y);
ans.push_back(z);
break;
}
}
if(pd) break;
}
}
else { //n>3
pd=1;
if(n%2==0&&m%2==0) {
for(int i=1;i<=n-2;i++) ans.push_back(1);
ans.push_back((m-n+2)/2);
ans.push_back((m-n+2)/2);
}
else if(n%2==0&&m%2==1) {
for(int i=2;i<=3;i++) ans.push_back(i);
for(int i=1;i<=n-4;i++) ans.push_back(2);
for(int i=1;i<=2;i++) ans.push_back((m-2*n+3)/2);
}
else if(n%2==1&&m%2==0) {
for(int i=1;i<=3;i++) ans.push_back(i);
for(int i=1;i<=n-5;i++) ans.push_back(1);
ans.push_back((m-n-1)/2);
ans.push_back((m-n-1)/2);
}
else if(n%2==1&&m%2==1) {
for(int i=1;i<=n-4;i++) ans.push_back(2);
ans.push_back(4);ans.push_back(7);
ans.push_back((m-2*n-3)/2);
ans.push_back((m-2*n-3)/2);
}
}
return pd;
}
void solve() {
ans.clear();
int n=read(),m=read();
if(n==1||m==n+1) {puts("NO");}
else if((m<2*n&&m%2==1)||(n%2==1&&m<2*n+7&&m%2==1)) {puts("NO");}
else {
if(!check(n,m)) puts("NO");
else {
cout<<"YES\n";
for(int i:ans) cout<<i<<" ";
puts("");
}
}
}
signed main()
{
int t=read();
while(t--) solve();
return 0;
}