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;
}