NOI2022 题解

upd:树上邻域数点如果 \(50\) 踩那国赛后会有的。可能是直接开写也可能是明年。截止笔试之前!

[NOI2022] 众数

首先看到题面第一句话发现这玩意跟摩尔投票一样。然后我们知道摩尔投票有结合律。于是我们如果不考虑前两个操作,那每个序列开个动态开点线段树维护一下摩尔投票,然后合并可以线段树合并,查询直接查每个序列摩尔投票的和,然后再统计一下这个答案在所有序列里的出现次数是否合法即可。

对于前两个操作,可以开个什么东西动态地维护序列,然后合并可以启发式合并。

然后是坑:首先这题赛时爆了一车零,因为 1e6 个 deque 会 MLE。需要 list。

然后是这题赛时一车人挂了 0-30 不等,因为查询的序列可能相同,因此统计次数要开 long long。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <list>
using namespace std;
int n,q;
list<int>v[1000010];
struct data{
    int val;
    long long cnt;
    data operator+(data s){
        if(val==s.val)return {val,cnt+s.cnt};
        else{
            if(cnt>s.cnt)return {val,cnt-s.cnt};
            else return {s.val,s.cnt-cnt};
        }
    }
};
struct node{
    #define lson tree[rt].ls
    #define rson tree[rt].rs
    int ls,rs;
    data val;
}tree[1000010<<5];
int t,rt[1000010];
void pushup(int rt){
    tree[rt].val=tree[lson].val+tree[rson].val;
}
void update(int &rt,int L,int R,int pos,int val){
    if(!rt)rt=++t;
    if(L==R){
        tree[rt].val.val=pos;
        tree[rt].val.cnt+=val;
        return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid)update(lson,L,mid,pos,val);
    else update(rson,mid+1,R,pos,val);
    pushup(rt);
}
int query(int rt,int L,int R,int pos){
    if(!rt)return 0;
    if(L==R)return tree[rt].val.cnt;
    int mid=(L+R)>>1;
    if(pos<=mid)return query(lson,L,mid,pos);
    else return query(rson,mid+1,R,pos);
}
int merge(int x,int y,int l,int r){
    if(!x||!y)return x|y;
    if(l==r){
        tree[x].val.cnt+=tree[y].val.cnt;
        return x;
    }
    int mid=(l+r)>>1;
    tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid);
    tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
    pushup(x);
    return x;
}
int main(){
    scanf("%d%d",&n,&q);int lim=n+q;
    for(int i=1;i<=n;i++){
        int l;scanf("%d",&l);
        while(l--){
            int x;scanf("%d",&x);
            v[i].push_back(x);update(rt[i],1,lim,x,1);
        }
    }
    while(q--){
        int od;scanf("%d",&od);
        if(od==1){
            int x,y;scanf("%d%d",&x,&y);
            v[x].push_back(y);
            update(rt[x],1,lim,y,1);
        }
        else if(od==2){
            int x;scanf("%d",&x);
            int y=v[x].back();v[x].pop_back();
            update(rt[x],1,lim,y,-1);
        }
        else if(od==3){
            data val={0,0};
            int m;scanf("%d",&m);
            long long cnt=0,sum=0;
            static int tmp[500010];
            for(int i=1;i<=m;i++){
                scanf("%d",&tmp[i]);
                val=val+tree[rt[tmp[i]]].val;
                sum+=v[tmp[i]].size();
            }
            for(int i=1;i<=m;i++)cnt+=query(rt[tmp[i]],1,lim,val.val);
            if((cnt<<1)<=sum)puts("-1");
            else printf("%d\n",val.val);
        }
        else{
            int x1,x2,x3;scanf("%d%d%d",&x1,&x2,&x3);
            rt[x1]=merge(rt[x1],rt[x2],1,lim);rt[x3]=rt[x1];
            if(v[x1].size()<v[x2].size()){
                while(!v[x1].empty())v[x2].push_front(v[x1].back()),v[x1].pop_back();
                swap(v[x2],v[x3]);
            }
            else{
                while(!v[x2].empty())v[x1].push_back(v[x2].front()),v[x2].pop_front();
                swap(v[x1],v[x3]);
            }
        }
    }
    return 0;
}

[NOI2022] 移除石子

一道玄学的题目。

首先考虑如何判定。先看 \(k=0,l=r\) 的部分,即给定一个序列如何判定是否有解。

观察到操作 \(2\) 只会有 \(3,4,5\) 长度的而且不会重复。

\(dp_{i,j,k}\) 为到 \(i\),钦定有 \(j\)\(i\) 之前的段延长到 \(i\),必须有 \(k\) 个延伸到 \(i+1\) 是否合法。转移考虑初值 \(dp_{1,0,0}=1\),从 \(i\) 转移到 \(i+1\) 时枚举从 \(i\) 开始的操作个数 \(l\),若 \(a_i-j-k-l\) 小于 \(0\) 或等于 \(1\) 则不合法,否则选出 \(p\in[k,k+j]\) 个钦定延伸到 \(i+1\),并使 \(dp_{i+1,p,l}=1\),答案即 \(dp_{n+1,0,0}\)。复杂分析或者打个大表一直拍可以得到 \(j,k\) 只取到 \([0,2]\) 一共九种状态。

考虑 \(k>0\) 的判断。容易发现大部分情况下 \(k\) 合法则 \(>k\) 的数也合法,特例是 \(k=1\) 且全 \(0\)\(k=1,n=3\) 且三个都是 \(1\)。于是设 \(f_{i,j,k}\) 为使 \(dp_{i,j,k}=1\) 至少添加的石子个数,转移同样枚举 \(l\)\(p\),容易得到至少在 \(i\) 出添加多少个石子。检查是否 \(f_{n+1,0,0}\le k\) 即可。

然后是关于计数。先对 \(k=0\) 计数:由于 \(j,k,l\le 2\),因此 \(\ge 8\) 的所有 \(a_i\) 都是等价的。打个大表一直拍可以把这个界减到 \(6\)。于是只要枚举 \([0,6]\)。考虑 dp 套 dp:先暴力搜出 \(a_i\) 为某个数时一种状态会转移到哪种状态。转移枚举状态 \(s\)\(a_i\),对于 \(a_i<6\) 那么只要 \(l_i\le a_i\le r_i\) 就有一倍贡献,\(a_i\ge 6\) 时则每个 \(a_i\) 都有贡献,转移很简单。

把两个东西拼起来,\(k\)\(100\) 所以 \(g\) 的值域是 \([0,101]\),看起来状态数是 \(102^9\)。但是搜一下会发现实际上 \(8000\) 多种,直接跑就行了。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
using namespace std;
const int mod=1000000007;
int n,k,num,ans,trans[10010][7],dp[2][10010],l[1010],r[1010];
map<vector<int>,int>mp;
int dfs(vector<int>g){
    if(mp.find(g)!=mp.end())return mp[g];
    mp[g]=num++;int id=mp[g];
    for(int i=0;i<7;i++){
        vector<int>tmp;
        for(int j=0;j<9;j++)tmp.push_back(101);
        for(int j=0;j<3;j++){
            for(int k=0;k<3;k++){
                if(g[3*j+k]==101)continue;
                for(int x=0;x<3;x++){
                    int val=i-j-k-x;
                    if(val<0)val=-val;
                    else val=val==1;
                    for(int l=k;l<=min(2,j+k);l++)tmp[3*l+x]=min(tmp[3*l+x],g[3*j+k]+val);
                }
            }
        }
        trans[id][i]=dfs(tmp);
    }
    return id;
}
int main(){
    int tim;scanf("%d",&tim);
    dfs({0,101,101,101,101,101,101,101,101});
    while(tim--){
        scanf("%d%d",&n,&k);ans=0;
        for(int i=1;i<=n;i++)scanf("%d%d",&l[i],&r[i]);
        memset(dp,0,sizeof(dp));dp[0][0]=1;
        int cur=0;
        for(int i=1;i<=n;i++){
            cur^=1;
            memset(dp[cur],0,sizeof(dp[cur]));
            for(int j=0;j<num;j++){
                if(!dp[cur^1][j])continue;
                for(int k=0;k<7;k++){
                    int ret=0;
                    if(k<6)ret=l[i]<=k&&k<=r[i];
                    else if(r[i]>=6)ret=r[i]-max(l[i],6)+1;
                    dp[cur][trans[j][k]]=(dp[cur][trans[j][k]]+1ll*ret*dp[cur^1][j])%mod;
                }
            }
        }
        for(pair<vector<int>,int>p:mp)if(p.first[0]<=k)ans=(ans+dp[cur][p.second])%mod;
        if(k==1){
            bool jud=true;
            for(int i=1;i<=n;i++)if(l[i]!=0){
                jud=false;break;
            }
            if(jud)ans=(ans-1+mod)%mod;
            if(n==3&&l[1]<=1&&r[1]>=1&&l[2]<=1&&r[2]>=1&&l[3]<=1&&r[3]>=1)ans=(ans-1+mod)%mod;
        }
        printf("%d\n",ans);
    }
    return 0;
}

[NOI2022] 树上邻域数点

不会 Top Tree。

但是你要是在笔试之前能给我点够 50 踩也不是不可以会。

[NOI2022] 挑战 NPC Ⅱ

看到 \(k\) 比较小,于是来个暴力一点的方法。

我们顺序搜两棵树,然后哈希值相同的直接匹配显然是最优的,哈希值不同的可以 \(O(k!)\) 暴力枚举哪对之间匹配然后暴力搜下去。

然后还需要加上一些玄学剪枝:首先如果 \(G\) 的儿子个数 / 子树大小小于 \(H\) 的儿子个数 / 子树大小显然不合法。然后如果子树大小相同直接返回哈希值相同。好像还有别的剪枝,不过不加也没什么关系,反正能过。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
typedef unsigned long long ull;
int n1,n2;
ull nxt(ull x){
	x^=x<<13;x^=x>>7;x^=x<<17;
	return x;
}
ull hs[100010][2];
vector<int>g[100010][2];
bool cmp1(int a,int b){
    return hs[a][0]<hs[b][0];
}
bool cmp2(int a,int b){
    return hs[a][1]<hs[b][1];
}
int size[100010][2];
void dfs1(int x,int id){
    hs[x][id]=size[x][id]=1;
    for(int v:g[x][id]){
        dfs1(v,id);
        hs[x][id]+=nxt(hs[v][id]);
        size[x][id]+=size[v][id];
    }
    if(id==0)sort(g[x][id].begin(),g[x][id].end(),cmp1);
    else sort(g[x][id].begin(),g[x][id].end(),cmp2);
}
bool dfs(int x,int y){
    if(g[x][0].size()<g[y][1].size())return false;
    if(size[x][0]<size[y][1])return false;
    if(size[x][0]==size[y][1])return hs[x][0]==hs[y][1];
    vector<int>A,B;
    int a=0,b=0;
    while(a<g[x][0].size()&&b<g[y][1].size()){
        if(hs[g[x][0][a]][0]==hs[g[y][1][b]][1])a++,b++;
        else if(hs[g[x][0][a]][0]<hs[g[y][1][b]][1])A.push_back(g[x][0][a]),a++;
        else B.push_back(g[y][1][b]),b++;
    }
    while(a<g[x][0].size())A.push_back(g[x][0][a]),a++;
    while(b<g[y][1].size())B.push_back(g[y][1][b]),b++;
    vector<int>p;
    for(int i=0;i<A.size();i++)p.push_back(i);
    do{
        bool jud=true;
        for(int i=0;i<B.size();i++){
            if(size[A[p[i]]][0]<size[B[i]][1]){
                jud=false;break;
            }
        }
        if(!jud)continue;
        for(int i=0;i<B.size();i++){
            if(!dfs(A[p[i]],B[i])){
                jud=false;break;
            }
        }
        if(jud)return true;
    }while(next_permutation(p.begin(),p.end()));
    return false;
}
int main(){
    int tim;scanf("%*d%d%*d",&tim);
    while(tim--){
        scanf("%d",&n1);
        int rt1,rt2;
        for(int i=1;i<=n1;i++){
            int f;scanf("%d",&f);
            if(f==-1)rt1=i;
            else g[f][0].push_back(i);
        }
        scanf("%d",&n2);
        for(int i=1;i<=n2;i++){
            int f;scanf("%d",&f);
            if(f==-1)rt2=i;
            else g[f][1].push_back(i);
        }
        dfs1(rt1,0);dfs1(rt2,1);
        if(dfs(rt1,rt2))puts("Yes");
        else puts("No");
        for(int i=1;i<=n1;i++)g[i][0].clear();
        for(int i=1;i<=n2;i++)g[i][1].clear();
    }
}

[NOI2022] 冒泡排序

首先显然可以让所有数只取到给定的 \(V\) 中的数。于是先离散化一下。然后最小化逆序对个数。

先看 B 部分分。确定了一堆单点,那么开个区间加查最小值的线段树随便扫一扫就行了。

然后是 C。容易发现把区间的最小值放在开头一定最优,于是变成了确定一堆单点,每个位置 \(\ge\) 某个数。也是线段树扫一扫就行了。

最后是比较困难的 A。根据上边的性质,我们必须把 \(1\) 铺满。然后看剩下的 \(0\) 的区间,它就变成了 C 部分。在开头安上然后线段树扫一扫就行了。

于是正解就十分显然了:我们把 \(V\) 从大到小排序,每次扫所有 \(\ge V\) 且没扫过的位置(可以用并查集记录扫没扫),在开头确定为最小值,然后给剩下的位置打个 \(\ge\) 这个最小值的标记。确定的部分可以树状数组扫一遍统计,不确定的线段树扫一扫。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
int n,m,lsh[1000010];
long long ans;
struct ques{
    int l,r,val;
}q[1000010];
vector<pair<int,int> >v[1000010];
struct BIT{
    int c[1000010];
    #define lowbit(x) (x&-x)
    void update(int x){
        while(x)c[x]++,x-=lowbit(x);
    }
    int query(int x){
        int sum=0;
        while(x<=m)sum+=c[x],x+=lowbit(x);
        return sum;
    }
}c;
int fa[1000010];
int find(int x){
    return x==fa[x]?fa[x]:fa[x]=find(fa[x]);
}
struct node{
    #define lson rt<<1
    #define rson rt<<1|1
    int mn,pos,lz;
}tree[4000010];
void pushup(int rt){
    tree[rt].mn=min(tree[lson].mn,tree[rson].mn);
    if(tree[rt].mn==tree[lson].mn)tree[rt].pos=tree[lson].pos;
    else tree[rt].pos=tree[rson].pos;
}
void pushtag(int rt,int val){
    tree[rt].mn+=val;tree[rt].lz+=val;
}
void pushdown(int rt){
    if(tree[rt].lz){
        pushtag(lson,tree[rt].lz);
        pushtag(rson,tree[rt].lz);
        tree[rt].lz=0;
    }
}
void build(int rt,int l,int r){
    tree[rt].mn=tree[rt].lz=0;tree[rt].pos=l;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(lson,l,mid);build(rson,mid+1,r);
}
void update(int rt,int L,int R,int l,int r,int val){
    if(l<=L&&R<=r){
        pushtag(rt,val);return;
    }
    pushdown(rt);
    int mid=(L+R)>>1;
    if(l<=mid)update(lson,L,mid,l,r,val);
    if(mid<r)update(rson,mid+1,R,l,r,val);
    pushup(rt);
}
pair<int,int> query(int rt,int L,int R,int l,int r){
    if(l<=L&&R<=r)return make_pair(tree[rt].mn,tree[rt].pos);
    pushdown(rt);
    int mid=(L+R)>>1;
    pair<int,int>val=make_pair(0x3f3f3f3f,0x3f3f3f3f);
    if(l<=mid)val=min(val,query(lson,L,mid,l,r));
    if(mid<r)val=min(val,query(rson,mid+1,R,l,r));
    return val;
}
int val[1000010],mn[1000010];
void solve(){
    scanf("%d%d",&n,&m);ans=0;
    for(int i=1;i<=n+1;i++)fa[i]=i,val[i]=mn[i]=0;
    for(int i=1;i<=m;i++)c.c[i]=0,v[i].clear();
    for(int i=1;i<=m;i++)scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].val),lsh[i]=q[i].val;
    sort(lsh+1,lsh+m+1);
    for(int i=1;i<=m;i++){
        q[i].val=lower_bound(lsh+1,lsh+m+1,q[i].val)-lsh;
        v[q[i].val].push_back(make_pair(q[i].l,q[i].r));
    }
    for(int i=m;i>=0;i--){
        if(v[i].empty())continue;
        sort(v[i].begin(),v[i].end());
        int pre=n+1;
        for(int j=v[i].size()-1;j>=0;j--){
            if(v[i][j].second<pre){
                int pos=find(v[i][j].first);
                if(pos>v[i][j].second){
                    puts("-1");return;
                }
                val[pos]=i;pre=pos;
            }
        }
        for(pair<int,int>p:v[i]){
            for(int j=find(p.first);j<=p.second;j=find(j))mn[j]=i,fa[j]=j+1;
        }
    }
    build(1,0,m+1);
    for(int i=1;i<=n;i++){
        if(val[i]){
            ans+=c.query(val[i]+1);
            c.update(val[i]);
            update(1,0,m+1,val[i]+1,m+1,1);
        }
    }
    for(int i=1;i<=n;i++){
        if(val[i]){
            update(1,0,m+1,val[i]+1,m+1,-1);
            update(1,0,m+1,0,val[i]-1,1);
        }
        else{
            pair<int,int>p=query(1,0,m+1,mn[i],m+1);
            ans+=p.first;
            if(p.second)update(1,0,m+1,0,p.second-1,1);
        }
    }
    printf("%lld\n",ans);
}
int main(){
    int tim;scanf("%d",&tim);
    while(tim--)solve();
    return 0;
}

[NOI2022] 二次整数规划问题

你觉得我会写吗?这篇博要是在 cnblogs 超过 20 个踩我就写。

妈妈生的,怎么这么能踩。

看题面发现这个限制非常奇怪。数据范围也很奇怪。然后 \(k=3,4,5\) 而且 \(3,4\) 一个 \(10\) 分更加奇怪。于是初步断定是个不可做题。

先看 \(k=3\)。显然如果一个数能取 \(2\) 那么取 \(2\) 更优,因为在 \(G\) 可以对所有数有贡献,在后半有 \(v_2\) 的贡献。那么现在要找到所有只能取 \(1,3\) 的数。

一个数什么时候只能取 \(1\)?一种显然的情况是 \(r=1\)。另一种情况是和一个只能取 \(1\) 的数之间有第二类约束且 \(b=0\)。那么不难找到所有这样的点,直接计算即可。

然后是 \(k=4\)。可以发现去掉必须选 \(1,4\) 的,剩下的不选 \(1,4\) 更优。进一步的,能选 \([2,k-1]\) 就选。然后需要决策剩下的选 \(2\) 还是选 \(3\)。观察对两部分的贡献:对于 \(G\),相互之间一定有贡献,如果选 \(2\) 那就是和 \(1\) 贡献,选 \(3\) 就是和 \(4\) 贡献。对于后边的部分,选 \(2\)\(v_2\),选 \(3\)\(v_3\)。每个位置是独立的,所以要么全 \(2\) 要么全 \(3\),比一下就好了。

但是我们这时候还有第二类约束的限制,决定了一些点只能选 \(2/3\)。但是这个是个形似最短路的东西:对于 \(i,j\) 的约束,可以使 \(j\) 的区间变成 \([l_j,r_j]\cap[l_i-b,r_i+b]\)。直接松弛 \(n\) 轮即可。于是我们在处理信息之前先松弛一遍得到真正的区间限制。

最后是 \(k=5\) 的情况。显然有结论:对于值域相同的数可以取到同一个值最优。然而第二类约束仍然是难以处理的。

确定 \(1,5\) 的个数之后,\(2,3,4\) 的个数 \(c_2,c_3,c_4\) 的总和是一定的,因此可以只用 \((c_2,c_4)\) 计算贡献。考虑找到能够贡献给答案的点:用 \((c_2,c_4)\) 的答案减掉 \((0,0)\) 的答案,化简一波式子变成:每次询问给定 \(C_1,C_2\),求 \((c_2-C_1)(c_4-C_2)\) 的最小值。

把所有点 \((x,y)\) 扔到二维平面上,那么所有点在 \(([\min x,\max x],[\min y,\max y])\) 矩形中且一定可以取到 \((\min x,\min y),(\max x,\min y),(\min x,\max y)\) 三个点。现在的问题变成平移矩形求 \(\min x\times y\)

对于矩形和一 / 二 / 四象限有交的情况,只需要上边三个点。对于全部在第三象限的情况,则是整个点集的右上凸包,根据集训队论文它的大小上界是 \(O(n^{\frac 23})\) 的。

于是套用最小乘积生成树的做法,只需要求两点间离连线最远的点。设两个点为 \((x_1,y_1),(x_2,y_2)\),那么选 \(2\)\(x_2-x_1\) 的代价,选 \(4\)\(y_1-y_2\) 的代价,选 \(3\)\(y_1-y_2+x_2-x_1\) 的代价,有若干组限制要求两个点之差不能超过某个数,求最小代价。这是切糕那题,直接跑网络流。

#include <iostream>
#include <algorithm>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,k,q,l[610],r[610],cnt[10],val[10],size[610],belong[610];
vector<pair<int,int> >g[610];
int calc(){
    int ans=0;
    for(int i=2;i<k;i++)ans+=val[i]*cnt[i];
    for(int i=1;i<=k;i++){
        for(int j=max(i-1,1ll);j<=min(i+1,k);j++)ans+=1000000*cnt[i]*cnt[j];
    }
    return ans;
}
struct gra{
    int v,w,next;
}edge[100010];
int t,head[3010];
void Add(int u,int v,int w){
    edge[++t].v=v;edge[t].w=w;edge[t].next=head[u];head[u]=t;
}
void add(int u,int v,int w){
    Add(u,v,w);Add(v,u,0);
}
int S,T,dis[3010],head2[3010];
bool bfs(int st){
    queue<int>q;
    for(int i=0;i<=T;i++)head2[i]=head[i],dis[i]=0;
    dis[st]=1;q.push(st);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=edge[i].next){
            if(edge[i].w&&!dis[edge[i].v]){
                dis[edge[i].v]=dis[x]+1;
                if(edge[i].v==T)return true;
                q.push(edge[i].v);
            }
        }
    }
    return false;
}
int dfs(int x,int flow){
    if(x==T)return flow;
    int sum=0;
    for(int &i=head2[x];i;i=edge[i].next){
        if(edge[i].w&&dis[edge[i].v]==dis[x]+1){
            int ret=dfs(edge[i].v,min(flow,edge[i].w));
            if(ret){
                flow-=ret;sum+=ret;
                edge[i].w-=ret;edge[i^1].w+=ret;
                if(!flow)break;
            }
            else dis[edge[i].v]=-1;
        }
    }
    return sum;
}
struct node{
    int x,y;
    node operator-(const node&s)const{
        return {x-s.x,y-s.y};
    }
    int operator^(const node&s)const{
        return x*s.y-y*s.x;
    }
};
vector<node>tubao;
node dinic(){
    while(bfs(S))dfs(S,inf);
    node ans={};
    for(int i=1;i<=n;i++){
        if(size[i]==-1)continue;
        if(l[i]>=2&&r[i]<=4){
            if(!dis[i])ans.x+=size[i];
            else if(dis[i+n])ans.y+=size[i];
        }
    }
    return ans;
}
void solve(node X,node Y){
    int val1=Y.x-X.x,val2=X.y-Y.y;t=1;
    S=0,T=2*n+1;
    for(int i=0;i<=T;i++)head[i]=0;
    for(int i=1;i<=n;i++){
        if(size[i]==-1)continue;
        if(l[i]>=2&&r[i]<=4){
            if(l[i]>=3)add(S,i,inf);
            else add(S,i,val1*size[i]);
            if(r[i]<=3)add(i+n,T,inf);
            else add(i+n,T,val2*size[i]);
            if(l[i]==4||r[i]==2)add(i,i+n,inf);
            else add(i,i+n,(val1+val2)*size[i]);
        }
    }
    for(int x=1;x<=n;x++){
        for(pair<int,int>p:g[x]){
            int v=p.first,w=p.second;
            if(belong[x]!=belong[v]&&size[belong[x]]!=-1&&size[belong[v]]!=-1&&w==1)add(belong[x]+n,belong[v],inf);
        }
    }
    node mid=dinic();
    if(((Y-mid)^(X-mid))<0){
        tubao.push_back(mid);
        solve(X,mid);solve(mid,Y);
    }
}
void build(){
    for(int i=1;i<=n;i++){
        if(size[i]==-1)continue;
        size[i]=-1;int cnt=0;
        queue<int>q;q.push(i);
        while(!q.empty()){
            int x=q.front();q.pop();cnt++;
            belong[x]=i;
            for(pair<int,int>p:g[x]){
                int v=p.first,w=p.second;
                if(size[v]!=-1&&!w){
                    size[v]=-1;q.push(v);
                }
            }
        }
        size[i]=cnt;
    }
    tubao.push_back({cnt[2],cnt[4]});
    int cnt2=0,cnt4=0;
    for(int i=1;i<=n;i++){
        if(l[i]==2)cnt2++;
        if(r[i]==4)cnt4++;
    }
    node X={cnt[2],cnt4},Y={cnt2,cnt[4]};
    tubao.push_back(X);tubao.push_back(Y);
    solve(X,Y);
}
int getans(node x){
    cnt[2]=x.x,cnt[4]=x.y,cnt[3]=n-cnt[1]-cnt[2]-cnt[4]-cnt[5];
    return calc();
}
signed main(){
    int tim;scanf("%lld%lld",&tim,&tim);
    while(tim--){
        scanf("%lld%lld%lld%lld",&k,&n,&m,&q);
        for(int i=1;i<=n;i++)scanf("%lld%lld",&l[i],&r[i]);
        for(int i=1;i<=m;i++){
            int u,v,w;scanf("%lld%lld%lld",&u,&v,&w);
            g[u].push_back(make_pair(v,w));g[v].push_back(make_pair(u,w));
        }
        for(int i=1;i<=n;i++){
            for(int x=1;x<=n;x++){
                for(pair<int,int>p:g[x]){
                    int v=p.first,w=p.second;
                    l[v]=max(l[v],l[x]-w);
                    r[v]=min(r[v],r[x]+w);
                }
            }
        }
        for(int i=1;i<=n;i++){
            if(r[i]!=1&&l[i]!=k)l[i]=max(l[i],2ll),r[i]=min(r[i],k-1);
            if(l[i]==r[i])cnt[l[i]]++;
            else cnt[0]++;
        }
        if(k==5)build();
        while(q--){
            for(int i=2;i<k;i++)scanf("%lld",&val[i]);
            if(k==3)printf("%lld\n",calc());
            if(k==4){
                int ans=0;
                cnt[2]+=cnt[0];ans=max(ans,calc());cnt[2]-=cnt[0];
                cnt[3]+=cnt[0];ans=max(ans,calc());cnt[3]-=cnt[0];
                printf("%lld\n",ans);
            }
            if(k==5){
                int ans=0;
                for(node x:tubao)ans=max(ans,getans(x));
                printf("%lld\n",ans);
            }
        }
        for(int i=1;i<=n;i++)g[i].clear(),size[i]=0;
        for(int i=0;i<=k;i++)cnt[i]=0;
        tubao.clear();
    }
    return 0;
}
posted @ 2023-07-13 20:55  gtm1514  阅读(816)  评论(17编辑  收藏  举报