NOIP2018提高组题解

时间:2024-10-12 21:36:19

D1T1:铺设道路

回忆NOIP2013D2T1 积木大赛,发现这两题唯一的区别就是一个是造山一个是填坑,而把填坑的操作反序就是造山,所以可以直接使用那道题的方法。

具体方法是,从左到右每次考虑新的一列,若这一列的坑比左边一列浅,那么可以在填左边一列的时候顺便填好这个坑(只要把所有右端点为i-1的操作右端点全部改为i即可),不需要任何操作。若这一列的坑比左边深,那么就必须先将这一列的坑填到与左边平齐,再让左边的操作顺带把这个坑填平。

于是有:若a[i]<=a[i-1],ans不变,否则ans+=a[i]-a[i-1]。

期望得分:100

复杂度:$O(n)$

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int n,ans,a[N]; int main(){
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
scanf("%d",&n);
rep(i,,n){
scanf("%d",&a[i]);
if (a[i]>a[i-]) ans+=a[i]-a[i-];
}
printf("%d\n",ans);
return ;
}

road

D1T2:货币系统

这题性质十分类似线性基的构造,首先证明两个结论:

1.最简系统一定是原系统的子集。

2.最简系统中任何一个面额不能被其余面额表出。

结论二显然。

若最简系统出现了一个原系统没有的数x,那么若这个数能被原系统表出,那么它的加入没有任何意义,可以删去,矛盾。

若不能被表出,那么最简系统就比原系统多表出了一个数x,矛盾。

结论一得证。

于是就有了运行策略,先将原系统中所有数从小到大排序(因为能表出某数的面额一定都不比那个数大),逐一判断每个数能否被当前最简系统表出,若不能则加入。

那么如何判断能否被表出呢,注意到值域不大,那么这个题的原型实际上就是一个完全背包问题,于是每次加入数的时候做一次完全背包即可。

期望得分:100

复杂度:$O(n*a_i)$

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int T,n,a[];
bool f[N]; int main(){
freopen("money.in","r",stdin);
freopen("money.out","w",stdout);
for (scanf("%d",&T); T--; ){
scanf("%d",&n); int mx=,ans=;
rep(i,,n) scanf("%d",&a[i]),mx=max(mx,a[i]);
rep(i,,mx) f[i]=; f[]=;
sort(a+,a+n+);
rep(i,,n){
if (f[a[i]]) continue;
ans++;
rep(j,a[i],mx) f[j]|=f[j-a[i]];
}
printf("%d\n",ans);
}
return ;
}

money

D1T3:赛道修建

十分套路的一道题。

算法一:m=1的情况就是要找一条直径。

期望得分:20

复杂度:$O(n)$

算法二:ai=1是一个菊花图,二分答案后尽量匹配出最多的长度>=mid的链。这个贪心或再套一层二分答案均可。

期望得分:20

复杂度:$O(n\log n)$

算法三:bi=ai+1是一条链,二分答案后贪心选取即可。若当前链长度已>=mid则断开,计数器+1。

期望得分:20

复杂度:$O(n\log n)$

以上部分通过数据分治可以得到55分。

算法四:分支不超过3,只要找到一个度为1的点作为根就是一棵二叉树。在树上做简单DP即可,具体可见满分做法。

期望得分:55

复杂度:$O(n\log n)$

以上部分分通过数据分治可以得到80分。

算法五:首先想到二分答案转化为可行性问题。问题转化为,能否找到至少m条长度不小于mid的不相交的道路。

考虑树形DP,这个问题要求最大化两个指标,一是道路条数最大化,二是道路条数最多的情况下每条道路长度最长。

于是设f[x]表示x的子树中最多能选出多少条长度不小于mid的互不相交的链,g[x]表示x的子树中,在已选出f[x]条合法链后,最多能从根往下延伸出多长的链(这条链不与已选出的f[x]条链相交,它将和x子树之外的点连接成为合法链)。

我们有结论:一个子树内,必定是先让合法链的个数最多(即最大化f),在此基础上再尽量让从根伸出的链最长(即最大化g)。

考虑转移,首先f[x]+=f[son],然后合并子树伸出的链。问题转化为找尽量多的不重复数对,是两两和不小于mid,且最后剩下来的最大的数最大。

先将所有数从小到大排序,然后从小到大考虑(若某个数已经配对则跳过),对于一个数x,在后面找到最小的数y使x+y>=mid,再找y以及y之后的第一个尚未配对的数x与之配对,f++,若不存在则x不与任何数配对,用x更新g。

可以证明,这个贪心策略一定是最优的,f和g在此策略下都能被最大化。

至于找y可以直接二分或单调指针扫描,找y后第一个可用的数可以用set或经典的并查集处理。

期望得分:100

复杂度:$O(n\log^2 n)$或$O(n\log n)$

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
int n,m,u,v,w,cnt,L,R,mid,ans,f[N],g[N],a[N],fa[N];
int h[N],to[N<<],nxt[N<<],val[N<<];
void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
int get(int x){ return (fa[x]==x) ? x : fa[x]=get(fa[x]); } void DP(int x,int Fa){
f[x]=; g[x]=; int tot=;
For(i,x) if ((k=to[i])!=Fa) DP(k,x),f[x]+=f[k];
For(i,x) if ((k=to[i])!=Fa) a[++tot]=g[k]+val[i];
if (!tot) return;
sort(a+,a+tot+);
while (tot && a[tot]>=mid) tot--,f[x]++;
if (!tot) return;
rep(i,,tot+) fa[i]=i;
rep(i,,tot-){
if (fa[i]!=i) continue;
int r=lower_bound(a+i+,a+tot+,mid-a[i])-a;
if (r>tot || a[i]+a[r]<mid) continue;
int t=get(r);
if (t>tot) continue;
f[x]++; fa[i]=get(i+); fa[t]=get(t+);
}
rep(i,,tot) if (fa[i]==i) g[x]=a[i];
} int main(){
freopen("track.in","r",stdin);
freopen("track.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,n) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w),R+=w;
while (L<=R){
mid=(L+R)>>; DP(,);
if (f[]>=m) ans=mid,L=mid+; else R=mid-;
}
printf("%d\n",ans);
return ;
}

track

D2T1:旅行

首先树的情况不难想到贪心策略,由于走的是一个dfs序,所以每次选最小的相邻的尚未走过的点走过去即可。

n=m的情况是一个环套树,由于最终的遍历过程中一定有一条边没有被遍历到,那么枚举这条边,删掉后再跑树的情况即可。

注意如果删的边不在环上会导致图不连通,此时显然得到的遍历序列长度小于n,判掉就好。

但这样n=m写的太丑的话可能会被卡常,所以最好在走跑dfs的时候实时判断是否比当前字典序是否比最优解大,若是则直接退出。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int n,m,d,tot,cnt,res[N],tmp[N],ans[N],h[N],to[N<<],nxt[N<<],E[N][N];
bool fl,fl1,vis[N];
struct edg{ int u,v; }e[N];
void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x){
if (fl) return;
res[++tot]=x; vis[x]=;
if (res[tot]<ans[tot]) fl1=;
if (res[tot]>ans[tot] && !fl1) { fl=; return; }
for (int i=h[x]; i; i=nxt[i]){
int k=to[i];
if (!((x==e[d].u && k==e[d].v) || (x==e[d].v && k==e[d].u)) && !vis[k]) dfs(k);
}
} int main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,n) ans[i]=n+;
rep(i,,m){
scanf("%d%d",&e[i].u,&e[i].v);
E[e[i].u][++tmp[e[i].u]]=e[i].v;
E[e[i].v][++tmp[e[i].v]]=e[i].u;
}
rep(i,,n){
if (!tmp[i]) continue;
sort(E[i]+,E[i]+tmp[i]+);
for (int j=tmp[i]; j; j--) add(i,E[i][j]);
}
if (m==n-){
dfs();
rep(i,,n) printf("%d ",res[i]);
return ;
}
for (d=; d<=m; d++){
tot=; rep(i,,n) vis[i]=;
fl=; fl1=; dfs();
if (tot<n || fl) continue;
rep(i,,n) ans[i]=res[i];
}
rep(i,,n) printf("%d ",ans[i]);
return ;
}

travel_1

或者直接找到环,只删环上的边。这在环很小的时候效果极佳。

 #include<vector>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
int n,m,u,v,cnt,tim,tot,top,res[N],ans[N];
int cir[N],vis[N],pre[N],h[N],to[N<<],nxt[N<<];
vector<int>a[N];
void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x){
vis[x]=; res[++top]=x;
For(i,x) if (!vis[k=to[i]]) dfs(k);
} void find(int x){
vis[x]=++tim;
For(i,x) if (!vis[k=to[i]]) pre[k]=x,find(k);
else if (vis[k]>vis[x]){
for (int j=k; j!=x; j=pre[j]) cir[++tot]=j;
cir[++tot]=x;
}
} void dfs2(int x,int fa){
vis[x]=; res[++top]=x;
For(i,x) if ((k=to[i])!=fa && !((x==u && k==v) || (x==v && k==u))) dfs2(k,x);
} int main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,m) scanf("%d%d",&u,&v),a[u].push_back(v),a[v].push_back(u);
rep(i,,n){
sort(a[i].begin(),a[i].end());
for (int j=a[i].size()-; j>=; j--) add(i,a[i][j]);
}
if (m<n){ dfs(); rep(i,,n) printf("%d ",res[i]); return ; }
find();
rep(i,,n) ans[i]=n+;
rep(i,,tot){
top=; rep(j,,n) vis[j]=;
u=cir[i]; v=cir[i%tot+];
dfs2(,); bool flag=;
rep(j,,n) if (res[j]!=ans[j]){ flag=res[j]<ans[j]; break; }
if (flag) rep(j,,n) ans[j]=res[j];
}
rep(i,,n) printf("%d ",ans[i]);
return ;
}

travel_2

D2T2:填数游戏

算法一:n,m<=3可以直接手算。

m=1时:$2^n$

n=1时:$2^m$

n=2,m=2:样例

n=2,m=3:手算得36

n=3,m=2:手算得36

n=3,m=3:样例

期望得分:20

复杂度:$O(1)$

开始理性分析下题目的性质:

题意是,对于任何一个点(x,y),从(x-1,y)出发的任意一条路径均不大于从(x,y-1)出发的任意一条路径。

那么有两个结论:

1.不存在$w_{x-1,y}=1$,$w_{x,y-1}=0$的情况。即任何一个对角线都是从左下到右上先1后0(或全0全1)。

2.若存在x,y使得$w_{x-1,y}=w_{x,y-1}$,那么(x,y)右下方的所有对角线上每个数都相等,及所有离(x,y)的曼哈顿距离相等的数都相等。

结论一显然。

若存在某个点(x1,y1)和(x2,y2) (x1,x2>=x0,y1,y2>=y0)使得$w_{x1,y1}=1$,$w_{x2,y2}=0$且这两个点到(x0,y0)的曼哈顿距离相等,那么可以从(x-1,y)出发走到(x1,y1),从(x,y-1)出发走到(x2,y2),那么前者路径的字典序一定会大于后者。

结论二得证。

这样证明了两个结论的必要性,理性分析一下可以发现,两个结论合起来就是充要条件。证明大概是讨论多种情况,这里不再叙述。

于是我们有了:

算法二:n=2可以忽视结论2,其中(1,1)和(n,m)可以随意选择,其它每对数有(0,0),(1,0),(1,1)三种取法(限于结论一不能取(0,1))。

很容易得出答案为$4*3^{m-1}$。或者状压DP也行,这里只要压两位所以几乎相当于普通DP。

期望得分:30

复杂度:$O(\log m)$

以上部分分通过数据分治可以得到50分。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,mod=1e9+;
int n,m,f[N][]; int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
if (n== || m==){ printf("%d\n",<<(n*m)); return ; }
if (n== && m==){ puts(""); return ; }
if (n== && m==){ puts(""); return ; }
if (n== && m==){ puts(""); return ; }
f[][]=f[][]=f[][]=f[][]=;
rep(i,,m){
f[i][]=f[i][]=((f[i-][]+f[i-][])%mod+(f[i-][]+f[i-][])%mod)%mod;
f[i][]=f[i][]=(f[i-][]+f[i-][])%mod;
}
printf("%d\n",((f[m][]+f[m][])%mod+(f[m][]+f[m][])%mod)%mod);
return ;
}

game(50分)

算法三:n=3时,结论二只会限制2,3行的一些数对相等,考虑状压DP解决。

f[i][S][0/1]表示,考虑到第i列,第i列的状态为S (0<=S<8),是/否存在结论二限制 的方案数。转移考虑这一列和上一列是否会构成限制。

期望得分:15

复杂度:$O(64m)$

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,mod=1e9+;
int n,m,ans,f[N][][]; int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m); int ed=(<<n)-;
rep(S,,ed) f[][S][]=;
rep(i,,m-){
rep(S,,ed) if (f[i][S][] || f[i][S][]){
rep(S1,,ed){
if ((S1& && !(S&)) || ((S1&) && !(S&))) continue;
if ((S1& && S&) || (!(S1&) && !(S&))) f[i+][S1][]=(f[i+][S1][]+f[i][S][])%mod;
else f[i+][S1][]=(f[i+][S1][]+f[i][S][])%mod;
if (!(S1&) && (S&)) continue;
f[i+][S1][]=(f[i+][S1][]+f[i][S][])%mod;
}
}
}
rep(S,,ed) ans=(ans+(f[m][S][]+f[m][S][])%mod)%mod;
printf("%d\n",ans);
return ;
}

game(DP)

算法四:根据结论一和结论二给暴搜剪枝,可以在15s内轻松打完8*8的表。

期望得分:15

复杂度:打表后$O(1)$

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,mod=1e9+;
int n,m,ans,tot,a[N][N],d[N];
struct P{ int x,y; }p[N];
void dfs(int x,int y); void Do(int x,int y){
if (x> && y<m && a[x][y]==a[x-][y+]) p[++tot]=(P){x,y+};
dfs(x,y+);
if (x> && y<m && a[x][y]==a[x-][y+]) tot--;
} void dfs(int x,int y){
if (x>n){ ans++; return; }
if (y>m) { dfs(x+,); return; }
rep(i,,tot) if (p[i].x<x && p[i].y<=y && y<m){
if (~a[x][y] && a[x][y]!=a[x-][y+]) { a[x][y]=-; return; }
a[x][y]=a[x-][y+];
}
if (~a[x][y]){ Do(x,y); a[x][y]=-; return; }
a[x][y]=; Do(x,y); a[x][y]=;
if (!(x> && y<m && a[x][y]<a[x-][y+])) Do(x,y);
a[x][y]=-;
} int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
for (n=; n<=; n++){
for (m=; m<=; m++){
rep(i,,n) rep(j,,m) a[i][j]=-;
ans=; dfs(,); printf("%d ",ans);
}
puts("");
}
return ;
}

game(打表)

以上部分分通过数据分治可以得到80分。

算法五:稍微把表打大一点,暴力找规律即可。不会证明,没什么好说的。

数据范围完全不知道出于什么意义。

有公式ans[n][n]=ans[n-1][n-1]*8-5*2^n,所以事实上出得多大都是没有问题的。

期望得分:100

复杂度:$O(\log n+\log m)$

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int mod=1e9+;
const int d[]={,,,,,,,,};
int n,m; int ksm(int a,int b){
int res=;
for (; b; a=1ll*a*a%mod,b>>=)
if (b & ) res=1ll*res*a%mod;
return res;
} int calc(int n,int m){
if (n==m) return d[n];
if (n==) return ksm(,m);
if (n==) return 4ll*ksm(,m-)%mod;
if (n==) return 112ll*ksm(,m-)%mod;
return ((3ll*d[n]-3ll*ksm(,n))*ksm(,m-n-)%mod+mod)%mod;
} int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
if (n>m) swap(n,m);
printf("%d\n",calc(n,m));
return ;
}

game(100)

D2T3:保卫王国

难度和知识点几乎脱离NOIP范围的一道题,有倍增做法但感觉难度很大,这里直讲动态DP做法。

算法一:n,m<=2000就是经典的《没有上司的舞会》,每次修改暴力重做一次树形DP即可。

期望得分:50

复杂度:$O(n^2)$

算法二:A1,A2保证是链,且修改只涉及到一个位置或两个相邻位置,记录前缀后缀即可。

期望得分:20

复杂度:$O(n)$

算法三:B1深度不超过100,可以发现每次修改只会涉及到它到根的链上的DP值,于是可以优化复杂度。

期望得分:8

复杂度:$O(100n)$

以上部分分通过数据分治可以获得72分。

 #include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e5+;
int n,m,tp[N],p[N],fa[N];
ll f[N][],g[N][];
vector<int>G[N];
char typ[];
void dp(int u,int ff)
{
f[u][]=;f[u][]=p[u];
for(int i=;i<(int)G[u].size();i++)
if(G[u][i]!=ff)
{
dp(G[u][i],u);
f[u][]+=f[G[u][i]][];
if(f[u][]>1e17)f[u][]=1e17;
f[u][]+=min(f[G[u][i]][],f[G[u][i]][]);
if(f[u][]>1e17)f[u][]=1e17;
}
if(!tp[u])f[u][]=1e17;
if(tp[u]==)f[u][]=1e17;
}
void dfs0(int u)
{
for(int i=;i<(int)G[u].size();i++)
if(G[u][i]!=fa[u])fa[G[u][i]]=u,dfs0(G[u][i]);
}
void modify(int u,int son)
{
g[u][]=f[u][],g[u][]=f[u][];
if(g[u][]!=1e17)
{
g[u][]-=f[son][];
g[u][]+=g[son][];
if(g[u][]>1e17)g[u][]=1e17;
}
if(g[u][]!=1e17)
{
g[u][]-=min(f[son][],f[son][]);
g[u][]+=min(g[son][],g[son][]);
if(g[u][]>1e17)g[u][]=1e17;
}
if(u!=)modify(fa[u],u);
}
int main()
{
scanf("%d%d%s",&n,&m,typ);
for(int i=;i<=n;i++)scanf("%d",&p[i]);
if(n<=&&m<=)
{
memset(tp,-,sizeof tp);
for(int i=,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
while(m--)
{
int a,x,b,y;
scanf("%d%d%d%d",&a,&x,&b,&y);
tp[a]=x,tp[b]=y;
dp(,);
tp[a]=tp[b]=-;
ll ans=min(f[][],f[][]);
if(ans==1e17)puts("-1");
else printf("%lld\n",ans);
}
return ;
}
if((typ[]=='B'||typ[]=='C')&&typ[]=='')
{
for(int i=,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
dfs0();
memset(tp,-,sizeof tp);
dp(,);
while(m--)
{
int b,y;
scanf("%*d%*d%d%d",&b,&y);
g[b][y]=f[b][y];g[b][y^]=1e17;
if(b!=)modify(fa[b],b);
if(g[][]==1e17)puts("-1");
else printf("%lld\n",g[][]);
}
return ;
}
if(typ[]=='A')for(int i=;i<n;i++)scanf("%*d%*d");
if(typ[]=='A'&&typ[]=='')
{
f[n][]=,f[n][]=p[n];
for(int i=n-;i;i--)f[i][]=f[i+][],f[i][]=min(f[i+][],f[i+][])+p[i];
g[][]=1e17,g[][]=p[];
for(int i=;i<=n;i++)g[i][]=g[i-][],g[i][]=min(g[i-][],g[i-][])+p[i];
while(m--)
{
int b,y;scanf("%*d%*d%d%d",&b,&y);
ll ans;
if(!y)ans=f[b+][]+g[b-][];
else ans=min(f[b+][],f[b+][])+min(g[b-][],g[b-][])+p[b];
printf("%lld\n",ans);
}
return ;
}
if(typ[]=='A'&&typ[]=='')
{
f[n][]=,f[n][]=p[n];
for(int i=n-;i;i--)f[i][]=f[i+][],f[i][]=min(f[i+][],f[i+][])+p[i];
g[][]=,g[][]=p[];
for(int i=;i<=n;i++)g[i][]=g[i-][],g[i][]=min(g[i-][],g[i-][])+p[i];
while(m--)
{
int a,x,b,y;scanf("%d%d%d%d",&a,&x,&b,&y);
if(a>b)swap(a,b),swap(x,y);
if(!x&&!y){puts("-1");continue;}
ll ans=f[b][y]+g[a][x];
printf("%lld\n",ans);
}
return ;
}
while(m--)puts("-1");
return ;
}

72分代码(From CTF)

算法四:树上带修改最小权覆盖问题。最小权覆盖=全集-最大权独立集。通过设inf和-inf控制选与不选。

带修改最大权独立集问题有经典的动态DP做法,通过链分治、矩乘和线段树加速。

树剖$O(n\log^2 n)$且常数极大,理论上极限数据可以卡到20s以上。

使用LCT或全局平衡二叉树可以做到$O(n\log n)$。

期望得分:100

复杂度:$O(n\log n)$

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define ls (x<<1)
#define rs (ls|1)
#define lson ls,L,mid
#define rson rs,mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=;
const ll inf=1e12;
ll sm,a[N],f[N][];
int n,m,u,v,s1,s2,q[N],top[N],L[N],R[N],id[N],son[N],sz[N],fa[N];
int cnt,tot,tim,h[N],to[N<<],nxt[N<<];
inline void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } struct Mat{
ll g[][];
Mat(){ g[][]=g[][]=g[][]=g[][]=; }
}val[N],T[N<<]; Mat operator *(const Mat &a,const Mat &b){
Mat c;
rep(i,,) rep(j,,) rep(k,,) c.g[i][k]=max(c.g[i][k],a.g[i][j]+b.g[j][k]);
return c;
} void dfs(int x){
sz[x]=; f[x][]=a[x];
For(i,x) if ((k=to[i])!=fa[x]){
fa[k]=x; dfs(k); sz[x]+=sz[k];
f[x][]+=max(f[k][],f[k][]); f[x][]+=f[k][];
if (sz[k]>sz[son[x]]) son[x]=k;
}
ll g0=,g1=a[x];
For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]) g0+=max(f[k][],f[k][]),g1+=f[k][];
val[x].g[][]=val[x].g[][]=g0; val[x].g[][]=g1;
} void build(int x,int L,int R){
if (L==R){ T[x]=val[id[L]]; return; }
int mid=(L+R)>>;
build(lson); build(rson);
T[x]=T[ls]*T[rs];
} void mdf(int x,int L,int R,int pos){
if (L==R){ T[x]=val[id[L]]; return; }
int mid=(L+R)>>;
if (pos<=mid) mdf(lson,pos); else mdf(rson,pos);
T[x]=T[ls]*T[rs];
} Mat que(int x,int L,int R,int l,int r){
if (L==l && r==R) return T[x];
int mid=(L+R)>>;
if (r<=mid) return que(lson,l,r);
else if (l>mid) return que(rson,l,r);
else return que(lson,l,mid)*que(rson,mid+,r);
} void solve(int x,ll k){
sm+=k-a[x]; val[x].g[][]+=k-a[x]; a[x]=k;
Mat od,nw;
while (x){
od=que(,,n,L[top[x]],R[top[x]]); mdf(,,n,L[x]);
nw=que(,,n,L[top[x]],R[top[x]]); x=fa[top[x]];
val[x].g[][]=val[x].g[][]+=max(nw.g[][],nw.g[][])-max(od.g[][],od.g[][]);
val[x].g[][]+=nw.g[][]-od.g[][];
}
} int main(){
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
scanf("%d%d%*s",&n,&m); q[]=; tot=;
rep(i,,n) scanf("%lld",&a[i]),sm+=a[i];
rep(i,,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
dfs();
rep(x,,n) For(i,x) if ((k=to[i])!=fa[x]) q[++tot]=k;
rep(i,,n) if (!top[u=q[i]]){
for (int k=u; k; k=son[k]) id[L[k]=++tim]=k,top[k]=u;
R[u]=tim;
}
build(,,n);
rep(i,,m){
scanf("%d%d%d%d",&u,&s1,&v,&s2); int t1=a[u],t2=a[v];
if (!s1 && !s2 && (fa[u]==v || fa[v]==u)){ puts("-1"); continue; }
if (s1) solve(u,a[u]-inf); else solve(u,a[u]+inf);
if (s2) solve(v,a[v]-inf); else solve(v,a[v]+inf);
Mat ans=que(,,n,L[],R[]); ll res=sm-max(ans.g[][],ans.g[][]);
res=(res%inf+inf)%inf; printf("%lld\n",res);
solve(u,t1); solve(v,t2);
}
return ;
}

defense(树剖)

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define ls (x<<1)
#define rs (ls|1)
#define lson ls,L,mid
#define rson rs,mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=;
const ll inf=1e12;
ll sm,a[N],s[N][];
int n,m,u,v,s1,s2,top[N],L[N],R[N],id[N],son[N],sz[N],f[N],fa[N],ch[N][];
int cnt,h[N],to[N<<],nxt[N<<];
inline void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } struct Mat{
ll g[][];
Mat(){ g[][]=g[][]=g[][]=g[][]=; }
}val[N],T[N]; Mat operator *(const Mat &a,const Mat &b){
Mat c;
rep(i,,) rep(j,,) rep(k,,) c.g[i][k]=max(c.g[i][k],a.g[i][j]+b.g[j][k]);
return c;
} void dfs(int x){
s[x][]=a[x];
For(i,x) if ((k=to[i])!=f[x]){
fa[k]=x; f[k]=x; dfs(k);
s[x][]+=max(s[k][],s[k][]); s[x][]+=s[k][];
}
ll g0=,g1=a[x];
For(i,x) if ((k=to[i])!=f[x] && k!=son[x]) g0+=max(s[k][],s[k][]),g1+=s[k][];
val[x].g[][]=val[x].g[][]=g0; val[x].g[][]=g1; T[x]=val[x];
} bool isroot(int x){ return (!f[x]) || (ch[f[x]][]!=x && ch[f[x]][]!=x); } void upd(int x){
if (ch[x][]) T[x]=T[ch[x][]]*val[x]; else T[x]=val[x];
if (ch[x][]) T[x]=T[x]*T[ch[x][]];
} void rot(int x){
int y=f[x],z=f[y],w=(ch[y][]==x);
if (!isroot(y)) ch[z][ch[z][]==y]=x;
f[x]=z; f[y]=x; f[ch[x][w^]]=y;
ch[y][w]=ch[x][w^]; ch[x][w^]=y; upd(y);
} void splay(int x){
while (!isroot(x)){
int y=f[x],z=f[y];
if (!isroot(y)) ((ch[z][]==y)^(ch[y][]==x))?rot(x):rot(y);
rot(x);
}
upd(x);
} void access(int x){
for (int y=; x; y=x,x=f[x]){
splay(x); Mat od,nw;
if (ch[x][]) nw=T[ch[x][]];
ch[x][]=y;
if (ch[x][]) od=T[ch[x][]];
val[x].g[][]=val[x].g[][]+=max(nw.g[][],nw.g[][])-max(od.g[][],od.g[][]);
val[x].g[][]+=nw.g[][]-od.g[][]; upd(x);
}
} void solve(int x,ll k){ access(x); splay(x); val[x].g[][]+=k-a[x]; a[x]=k; upd(x); } int main(){
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
scanf("%d%d%*s",&n,&m);
rep(i,,n) scanf("%lld",&a[i]),sm+=a[i];
rep(i,,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
dfs();
rep(i,,m){
scanf("%d%d%d%d",&u,&s1,&v,&s2); int t1=a[u],t2=a[v];
if (!s1 && !s2 && (fa[u]==v || fa[v]==u)){ puts("-1"); continue; }
if (s1) solve(u,a[u]-inf); else solve(u,a[u]+inf);
if (s2) solve(v,a[v]-inf); else solve(v,a[v]+inf);
splay(); ll res=sm-max(T[].g[][],T[].g[][]);
res=(res%inf+inf)%inf; printf("%lld\n",res);
solve(u,t1); solve(v,t2);
}
return ;
}

defense(LCT)