NOI2015D1T1
題目大意:$T$ 組數據。在一個程序中有無數個變量 $x_i$。現在有 $n$ 條限制,形如 $x_i=x_j$ 或者 $x_i\ne x_j$。(對於每個限制 $i,j$ 給定)問是否存在一種合法的賦值方案滿足所有限制。
$1\le T\le 10,1\le n\le 10^5,1\le i,j\le 10^9$。
普及難度。先把所有編號離散化,然后對於每個相等的限制,把這兩個變量塞到一個集合里(並查集)。最后對於每個不等的限制,判斷兩個變量是否在一個集合里。
時間復雜度 $O(Tn\log n)$。

#include<bits/stdc++.h> using namespace std; int t,n,u[100010],v[100010],op[100010],tmp[200020],fa[200020]; int getfa(int x){ return fa[x]==x?x:fa[x]=getfa(fa[x]); } void comb(int u,int v){ int fu=getfa(u),fv=getfa(v); if(fu!=fv) fa[fu]=fv; } bool same(int u,int v){ int fu=getfa(u),fv=getfa(v); return fu==fv; } int main(){ scanf("%d",&t); while(t--){ memset(u,0,sizeof(u)); memset(v,0,sizeof(v)); memset(op,0,sizeof(op)); memset(tmp,0,sizeof(tmp)); scanf("%d",&n); for(int i=1;i<=2*n;i++) fa[i]=i; for(int i=1;i<=n;i++){ scanf("%d%d%d",u+i,v+i,op+i); tmp[i*2-1]=u[i]; tmp[i*2]=v[i]; } sort(tmp+1,tmp+2*n+1); unique(tmp+1,tmp+2*n+1); bool flag=true; for(int i=1;i<=n;i++){ u[i]=lower_bound(tmp+1,tmp+2*n+1,u[i])-tmp; v[i]=lower_bound(tmp+1,tmp+2*n+1,v[i])-tmp; } for(int i=1;i<=n;i++) if(op[i]==1) comb(u[i],v[i]); for(int i=1;i<=n;i++) if(op[i]==0) if(same(u[i],v[i])){ flag=false;break; } if(flag) printf("YES\n"); else printf("NO\n"); } }
NOI2015D1T2
題目大意:一棵 $n$ 個點的樹,點編號從 $0$ 到 $n-1$,$0$ 為根。一開始每個點點權均為 $0$。接下來有 $q$ 個操作:
- $\text{install}\ u$ 表示將 $u$ 到根的路徑上的點點權都變為 $1$;
- $\text{uninstall}\ u$ 表示將 $u$ 子樹中所有點點權都變為 $0$。
每次操作完后,詢問有多少個點的點權在這次操作中發生了變化。
$1\le n,q\le 10^5$。
樹剖裸題。時間復雜度 $O(n+q\log^2n)$。

#include<iostream> #include<cstdio> using namespace std; const int maxn=100010; struct edge{ int to,nxt; }e[maxn]; int n,q,el,dfn,head[maxn]; int dep[maxn],size[maxn],son[maxn],fa[maxn]; int id[maxn],w[maxn],top[maxn]; int sum[maxn<<2],set[maxn<<2]; inline void add(int u,int v){ e[++el]=(edge){v,head[u]}; head[u]=el; } void dfs1(int u,int f,int d){ dep[u]=d; fa[u]=f; size[u]=1; int maxson=-1; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; dfs1(v,u,d+1); size[u]+=size[v]; if(size[v]>maxson){ maxson=size[v];son[u]=v; } } } void dfs2(int u,int topf){ id[u]=++dfn; top[u]=topf; if(!son[u]) return; dfs2(son[u],topf); for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(v==son[u]) continue; dfs2(v,v); } } inline void pushup(int t){ sum[t]=sum[t<<1]+sum[t<<1|1]; } inline void pushdown(int l,int r,int t){ if(~set[t]){ int mid=l+r>>1; set[t<<1]=set[t]; set[t<<1|1]=set[t]; sum[t<<1]=set[t]*(mid-l+1); sum[t<<1|1]=set[t]*(r-mid); set[t]=-1; } } void build(int l,int r,int t){ if(l==r){ set[t]=-1;return; } int mid=l+r>>1; build(l,mid,t<<1); build(mid+1,r,t<<1|1); } void setstate(int L,int R,int l,int r,int x,int t){ if(L>=l && R<=r){ set[t]=x;sum[t]=x*(R-L+1);return; } pushdown(L,R,t); int mid=L+R>>1; if(mid>=l) setstate(L,mid,l,r,x,t<<1); if(mid<r) setstate(mid+1,R,l,r,x,t<<1|1); pushup(t); } int getroot(){ pushdown(1,n,1); return sum[1]; } int install(int u){ int pre=getroot(); while(u){ setstate(1,n,id[top[u]],id[u],1,1); u=fa[top[u]]; } return getroot()-pre; } int uninstall(int u){ int pre=getroot(); setstate(1,n,id[u],id[u]+size[u]-1,0,1); return pre-getroot(); } int main(){ scanf("%d",&n); for(int i=1;i<=n-1;i++){ int x; scanf("%d",&x); add(x+1,i+1); } dfs1(1,0,1);dfs2(1,1); build(1,n,1); scanf("%d",&q); for(int i=1;i<=q;i++){ char str[10];int x; scanf("%s%d",str,&x);x++; if(str[0]=='i') printf("%d\n",install(x)); else printf("%d\n",uninstall(x)); } }
NOI2015D2T1
題目大意:有 $n$ 個字符串,第 $i$ 個在文章中出現了 $w_i$ 次。現在要把每個字符串替換成一個 $k$ 進制字符串(每個字符都是 $0$ 到 $k-1$ 的整數)。假設第 $i$ 個字符串被替換成了 $s_i$,那么要求對於任意 $i\ne j$ 都有 $s_i$ 不是 $s_j$ 的前綴。現在請求出替換后文章最小的長度($\sum w_i|s_i|$),在此基礎上求出 $\max(|s_i|)$ 的最小值。
$1\le n\le 10^5,2\le k\le 9,0\le w_i\le 10^{11}$。
實際上就是哈夫曼樹的定義。那么求個 $k$ 進制哈夫曼樹即可。
時間復雜度 $O(k+n\log n)$。

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=100010; #define FOR(i,a,b) for(int i=(a);i<=(b);i++) #define ROF(i,a,b) for(int i=(a);i>=(b);i--) #define MEM(x,v) memset(x,v,sizeof(x)) inline ll read(){ char ch=getchar();ll x=0,f=0; while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return f?-x:x; } int n,k;ll ans; struct hhh{ ll w;int h; bool operator<(const hhh &h)const{ if(w!=h.w) return w>h.w; return this->h>h.h; } }; priority_queue<hhh> pq; int main(){ n=read();k=read(); FOR(i,1,n) pq.push((hhh){read(),1}); if((n-1)%(k-1)!=0) FOR(i,1,k-1-(n-1)%(k-1)) pq.push((hhh){0,1}); while(pq.size()!=1){ ll sum=0;int hei=0; FOR(i,1,k){ hhh h=pq.top();pq.pop(); sum+=h.w;hei=max(hei,h.h); } pq.push((hhh){sum,hei+1});ans+=sum; } printf("%lld\n%d\n",ans,pq.top().h-1); }
NOI2015D2T2
題目大意:有一個長度為 $n$ 的小寫字母字符串 $s$,第 $i$ 個字符有權值 $a_i$。對於這個字符串的任意兩個不同后綴 $p,q$,定義 $lcp(p,q)$ 為兩個后綴的最長公共前綴的長度。現在對於每個 $0\le i\le n-1$,求出對於所有 $lcp(p,q)\ge i$ 的 $p,q$,$a_p\times a_q$ 的和和最大值。
$1\le n\le 3\times 10^5,|a_i|\le 10^9$。
(想看並查集做法的看別人的題解吧……)
NOI2016D1T1
題目大意:如果一個字符串可以被拆分為 $AABB$ 的形式,其中 $A$ 和 $B$ 是任意非空字符串,則我們稱該字符串的這種拆分是優秀的。現在給出一個長度為 $n$ 的小寫字母字符串 $S$,我們需要求出,在它所有子串的所有拆分方式中,優秀拆分的總個數。$T$ 組數據。
$1\le n\le 30000,1\le T\le 10$。
考慮計算以 $i$ 結尾的 $AA$ 有多少個(設為 $a_i$),以 $i$ 開頭的 $AA$ 有多少個(設為 $b_i$),答案即為 $\sum a_ib_{i+1}$。
下面以求 $a_i$ 為例。枚舉 $A$ 的長度 $l$,然后考慮 $l,2l,3l\dots\lfloor\frac{n}{l}\rfloor l$ 這些位置。
對於 $il$ 和 $(i+1)l$,求出這兩個后綴的最長公共前綴和這兩個前綴的最長公共后綴,設他們的長度分別為 $x$ 和 $y$。(與 $l$ 取最小值)
(從題解偷張圖)
那么會發現,$x+y<l$ 時,紅色熒光筆部分是無法匹配的,所以不存在滿足要求的 $AA$ 串。
而當 $x+y\ge l$ 時:
發現粉色和棕色的都是合法的 $AA$ 串。也就是說會對綠色熒光筆部分的每一個 $a$ 都產生 $1$ 的貢獻。(這個區間是 $[(i+1)l-y+l-1,(i+1)l+x-1]$)
$b$ 同理。
求 $x,y$ 可以用后綴數組做到 $O(1)$,區間加可以用差分做到 $O(1)$。
總時間復雜度 $O(Tn\log n)$。

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=30030; #define PB push_back #define MP make_pair #define lson o<<1,l,mid #define rson o<<1|1,mid+1,r #define FOR(i,a,b) for(int i=(a);i<=(b);i++) #define ROF(i,a,b) for(int i=(a);i>=(b);i--) #define MEM(x,v) memset(x,v,sizeof(x)) inline int read(){ char ch=getchar();int x=0,f=0; while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return f?-x:x; } struct Suffix_Array{ char s[maxn]; int n,m,cnt[maxn],sa[maxn],rak[maxn],tmp[maxn],h[maxn][17],logt[maxn]; void radix_sort(){ MEM(cnt,0); FOR(i,1,n) cnt[rak[tmp[i]]]++; FOR(i,1,m) cnt[i]+=cnt[i-1]; ROF(i,n,1) sa[cnt[rak[tmp[i]]]--]=tmp[i]; } void build(char s[]){ MEM(sa,0);MEM(rak,0);MEM(tmp,0);MEM(h,0); n=strlen(s+1);m=26; FOR(i,1,n) rak[tmp[i]=i]=s[i]-'a'+1; radix_sort(); for(int d=1,p=1;p<n;m=p,d<<=1){ p=0; FOR(i,1,d) tmp[++p]=n-d+i; FOR(i,1,n) if(sa[i]>d) tmp[++p]=sa[i]-d; radix_sort();swap(rak,tmp); rak[sa[1]]=p=1; FOR(i,2,n) rak[sa[i]]=(tmp[sa[i]]==tmp[sa[i-1]] && tmp[sa[i]+d]==tmp[sa[i-1]+d])?p:++p; } int k=0; FOR(i,1,n){ if(k) k--; for(int j=sa[rak[i]-1];s[i+k]==s[j+k];k++); h[rak[i]][0]=k; } logt[1]=0;FOR(i,2,n) logt[i]=logt[i>>1]+1; FOR(j,1,logt[n]+1) FOR(i,1,n-(1<<j)+1) h[i][j]=min(h[i][j-1],h[i+(1<<j-1)][j-1]); } int LCP(int x,int y){ x=rak[x];y=rak[y]; if(x>y) swap(x,y); x++; int k=logt[y-x+1]; return min(h[x][k],h[y-(1<<k)+1][k]); } }nor,rev; int n; char str[maxn]; ll ans,a[maxn],b[maxn]; int main(){ for(int t=read();t;t--){ scanf("%s",str+1);n=strlen(str+1); nor.build(str); for(int i=1,j=n;i<j;i++,j--) swap(str[i],str[j]); rev.build(str); MEM(a,0);MEM(b,0);ans=0; FOR(l,1,n/2){ for(int i=l,j=l<<1;j<=n;i+=l,j+=l){ int x=min(l,nor.LCP(i,j)),y=min(l-1,rev.LCP(n-i+2,n-j+2)); if(x+y>=l){ a[j+x-(x+y-l+1)]++;a[j+x]--; b[i-y+(x+y-l+1)]--;b[i-y]++; } } } FOR(i,1,n) a[i]+=a[i-1],b[i]+=b[i-1]; FOR(i,1,n-1) ans+=a[i]*b[i+1]; printf("%lld\n",ans); } }
NOI2016D2T1
題目大意:有 $n$ 個區間 $[l_i,r_i]$。現在你要選出 $m$ 個區間,使得至少有一個整點被所有的這些區間覆蓋到。對於一個選取方案價值是所有區間的最大長度與最小長度的差。問最小價值。如果沒有合法選取方案輸出 $-1$。
$1\le n\le 5\times 10^5,1\le m\le 2\times 10^5,0\le l_i\le r_i\le 10^9$。
首先把區間按長度從小到大排序。枚舉最短的區間,然后找最長的區間,使得至少有一個點被覆蓋至少 $m$ 次(然后就能從里面選出這 $m$ 個區間,是符合要求的)。想讓價值最小,就是找到最前面的最長區間。發現這個區間不降,所以可以尺取法。
至少一個點覆蓋 $m$ 次,還有區間加操作,可以變成求區間最大值的線段樹。注意在線段樹上操作最好先離散化。
時間復雜度 $O(n\log n)$。

#include<bits/stdc++.h> using namespace std; const int maxn=500050,maxm=200020; int n,m,sz,tmp[maxn*2]; int cnt,rt,mx[maxn*4],add[maxn*4],ch[maxn*4][2]; struct interval{ int l,r,len; bool operator<(const interval &i)const{return len<i.len;} }seg[maxn]; inline int binary(int x){return lower_bound(tmp+1,tmp+sz+1,x)-tmp;} inline void pushup(int x){mx[x]=max(mx[ch[x][0]],mx[ch[x][1]]);} inline void pushdown(int x){ if(add[x]){ add[ch[x][0]]+=add[x];add[ch[x][1]]+=add[x]; mx[ch[x][0]]+=add[x];mx[ch[x][1]]+=add[x]; add[x]=0; } } void build(int &x,int l,int r){ x=++cnt;if(l==r) return; int mid=(l+r)>>1; build(ch[x][0],l,mid);build(ch[x][1],mid+1,r); } void update(int x,int l,int r,int ql,int qr,int v){ if(l>=ql && r<=qr) return void((add[x]+=v,mx[x]+=v)); int mid=(l+r)>>1; pushdown(x); if(mid>=ql) update(ch[x][0],l,mid,ql,qr,v); if(mid<qr) update(ch[x][1],mid+1,r,ql,qr,v); pushup(x); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d%d",tmp+i*2-1,tmp+i*2); seg[i]=(interval){tmp[i*2-1],tmp[i*2],tmp[i*2]-tmp[i*2-1]}; } sort(tmp+1,tmp+2*n+1);sort(seg+1,seg+n+1); build(rt,1,sz=unique(tmp+1,tmp+2*n+1)-tmp-1); for(int i=1;i<=n;i++) seg[i].l=binary(seg[i].l),seg[i].r=binary(seg[i].r); int cl=1,ans=INT_MAX; for(int i=1;i<=n;i++){ update(rt,1,sz,seg[i].l,seg[i].r,1); while(cl<=i && mx[rt]>=m){ ans=min(ans,seg[i].len-seg[cl].len); update(rt,1,sz,seg[cl].l,seg[cl].r,-1); cl++; } } printf("%d\n",ans==INT_MAX?-1:ans); }
NOI2017D1T1
題目大意:有一個大整數 $x$,一開始是 $0$。一共有 $n$ 個操作,有兩種操作:
- $1\ a\ b$ 表示將 $x$ 加上 $a\times 2^b$
- $2\ k$ 表示詢問 $x$ 的二進制表示下的第 $k$ 位
$1\le n\le 10^6,|a|\le 10^9,0\le b,k\le 3\times 10^7$,任意時刻 $x\ge 0$。
先考慮修改操作。
有一個結論:如果每次都加正數(以下把加正數叫加,加負數叫減),那么每次暴力進位復雜度均攤 $O(1)$。
然而這是只有加操作,再有減操作時,就不能暴力進退位了。
那么可以考慮記錄兩個數(記為 $add$ 和 $sub$),分別表示加了多少和減了多少,目前每一次操作均攤 $O(1)$。
再考慮詢問操作。
發現當有一位發生退位時,那么從這位的高一位開始,一整段連續的 $0$(也就是這些位上 $add$ 都和 $sub$ 相等)都會變成 $1$,這段后的 $1$ 會變成 $0$。
所以對於一位 $k$,從這一位的低一位開始,找到第一位 $add\ne sub$ 的位置,如果這一位上 $add>sub$(不借位),那么第 $k$ 位不會變,否則第 $k$ 位會變。
問題變為比較兩個超多位數的數的大小。
可以維護所有 $add\ne sub$ 的位(用 set 存),然后求出比 $k$ 低的第一位不同的位,比較即可。
修改時可以簡單修改一下這個 set。
時間復雜度 $O(n\log n)$。
下面的代碼壓了 $30$ 位。

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1111111; #define FOR(i,a,b) for(int i=(a);i<=(b);i++) #define ROF(i,a,b) for(int i=(a);i>=(b);i--) #define MEM(x,v) memset(x,v,sizeof(x)) inline int read(){ int x=0,f=0;char ch=getchar(); while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return f?-x:x; } int n; ll add[maxn],sub[maxn]; set<int,greater<int> > s; int main(){ n=read();read();read();read(); while(n--){ int op=read(),x=read(),y; if(op==1){ y=read(); int a=y/30,b=y%30,cnt=0; if(x>0){ add[a]+=(ll)x<<b; while(add[a]>>30 || cnt<=5){ if(!(add[a]>>30)) cnt++; add[a+1]+=add[a]>>30; add[a]&=(1<<30)-1; if(add[a]!=sub[a]) s.insert(a); else if(s.count(a)) s.erase(a); a++; } } else if(x<0){ sub[a]+=(ll)(-x)<<b; while(sub[a]>>30 || cnt<=5){ if(!(sub[a]>>30)) cnt++; sub[a+1]+=sub[a]>>30; sub[a]&=(1<<30)-1; if(add[a]!=sub[a]) s.insert(a); else if(s.count(a)) s.erase(a); a++; } } } else{ int a=x/30,b=x%30,t1=add[a]&((1<<b)-1),t2=sub[a]&((1<<b)-1),t=((add[a]^sub[a])>>b)&1; set<int,greater<int> >::iterator it=s.lower_bound(a-1); if(t1>t2 || t1==t2 && (it==s.end() || add[*it]>sub[*it])) printf("%d\n",t); else printf("%d\n",t^1); } } }
NOI2017D2T1
題目大意:有一個長度為 $n$ 的字符串 $S$,只包含 $\text{a,b,c,x}$。現在你要把每個字符都替換成 $\text{A,B,C}$ 中的一個,其中 $\text{a}$ 不能替換成 $\text{A}$,$\text{b}$ 不能替換成 $\text{B}$,$\text{c}$ 不能替換成 $\text{C}$,$\text{x}$ 任意。另外有 $m$ 個限制 $p_1,c_1,p_2,c_2$,表示如果第 $p_1$ 個字母被替換成了 $c_1$,那么第 $p_2$ 個字母就一定要被替換成 $c_2$。請求出一種合法方案。如果無解輸出 $-1$。
$1\le n\le 50000,0\le m\le 100000$,$\text{x}$ 的個數(稱為 $d$) $\le 8$。
先考慮沒有 $d=0$ 怎么做。發現是 2-SAT 裸題。
然后可以枚舉把每個 $\text{x}$ 看成是 $\text{a}$ 還是 $\text{b}$ 還是 $\text{c}$。所有的合法情況一定都被枚舉到了。
時間復雜度 $O(3^d(n+m))$,不能通過。
發現只需要枚舉 $\text{a}$ 和 $\text{b}$ 就夠了,也已經把選 $\text{A,B,C}$ 的情況都考慮到了。
時間復雜度 $O(2^d(n+m))$。

#include<bits/stdc++.h> using namespace std; const int maxn=50050; #define MP make_pair #define PB push_back #define lson o<<1,l,mid #define rson o<<1|1,mid+1,r #define FOR(i,a,b) for(int i=(a);i<=(b);i++) #define ROF(i,a,b) for(int i=(a);i>=(b);i--) #define MEM(x,v) memset(x,v,sizeof(x)) inline int read(){ char ch=getchar();int x=0,f=0; while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return f?-x:x; } int n,d,m,c,ai[maxn*2],bi[maxn*2],id[maxn]; int stk[maxn*2],tp,scnt,scc[maxn*2],dfn[maxn*2],low[maxn*2],dcnt; int el,head[maxn*2],to[maxn*4],nxt[maxn*4]; char s[maxn],hai[maxn*2],hbi[maxn*2]; bool nota[10],vis[maxn*2]; inline void add(int u,int v){ to[++el]=v;nxt[el]=head[u];head[u]=el; } void tarjan(int u){ dfn[u]=low[u]=++dcnt; vis[stk[++tp]=u]=true; for(int i=head[u];i;i=nxt[i]){ int v=to[i]; if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]); else if(vis[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]){ scnt++; do{ scc[stk[tp]]=scnt; vis[stk[tp]]=false; }while(stk[tp--]!=u); } } inline int type(char a,char b){ return a=='a' && b=='c' || a=='b' && b=='c' || a=='c' && b=='b'; } void SAT_2(){ tp=scnt=dcnt=el=0;MEM(stk,0);MEM(scc,0);MEM(dfn,0);MEM(low,0);MEM(head,0);MEM(to,0);MEM(nxt,0); FOR(i,1,n) if(id[i]) s[i]=nota[id[i]]?'a':'b'; FOR(i,1,m){ if(s[ai[i]]==hai[i]) continue; if(s[bi[i]]==hbi[i]) add(2*ai[i]+type(s[ai[i]],hai[i]),2*ai[i]+!type(s[ai[i]],hai[i])); else add(2*ai[i]+type(s[ai[i]],hai[i]),2*bi[i]+type(s[bi[i]],hbi[i])),add(2*bi[i]+!type(s[bi[i]],hbi[i]),2*ai[i]+!type(s[ai[i]],hai[i])); } FOR(i,2,2*n+1) if(!dfn[i]) tarjan(i); FOR(i,1,n) if(scc[2*i]==scc[2*i+1]) return; FOR(i,1,n){ if(s[i]=='a') putchar(scc[2*i]<scc[2*i+1]?'B':'C'); if(s[i]=='b') putchar(scc[2*i]<scc[2*i+1]?'A':'C'); if(s[i]=='c') putchar(scc[2*i]<scc[2*i+1]?'A':'B'); } exit(0); } void dfs(int dep){ if(dep>d) return SAT_2(); dfs(dep+1); nota[dep]=true; dfs(dep+1); nota[dep]=false; } int main(){ n=read();d=read(); scanf("%s",s+1); FOR(i,1,n) if(s[i]=='x') id[i]=++c; m=read(); FOR(i,1,m){ ai[i]=read(); while(hai[i]<'A' || hai[i]>'C') hai[i]=getchar();hai[i]+='a'-'A'; bi[i]=read(); while(hbi[i]<'A' || hbi[i]>'C') hbi[i]=getchar();hbi[i]+='a'-'A'; } dfs(1); printf("-1"); }
NOI2018D1T1
題目大意:$T$ 組數據。給一個 $n$ 個點 $m$ 條邊的無向連通圖,每條邊有長度 $l_i$ 和權值 $a_i$。$q$ 次詢問,每次給定起點 $u$ 和權值下限 $x$,問對於所有能只經過 $a_i>x$ 的邊就走到 $u$ 的點,到 $1$ 的最短路的最小值。強制在線。
$1\le T\le 3,1\le n\le 2\times 10^5,0\le m,q\le 4\times 10^5$。
首先預處理每個點到 $1$ 的最短路。
將邊按 $a_i$ 從大到小排序,然后建出 kruskal 重構樹,那么 $u$ (新點,代表原來的邊)的子樹中就是可以只經過超過 $a_u$ 的邊互達的點。每次詢問時,從點 $u$(原點)倍增,跳到第一個父親的 $a\le x$ 的位置,答案就是這棵子樹中最短路的最小值。
時間復雜度 $O(T(m\log n+m\log m+q\log n))$。

#include<bits/stdc++.h> using namespace std; #define mem(x) (memset(x,0,sizeof(x))) typedef long long ll; const int maxn=200020,maxm=400040; struct edge1{ int u,v,w; bool operator<(const edge1 e)const{ return w>e.w; } }e1[maxm]; struct edge2{ int v,w,nxt; }e2[maxm<<1],e3[maxn<<1]; struct state{ int u;ll dis; bool operator<(const state s)const{ return dis>s.dis; } }; int t,n,m,q,k,s,lastans; int el2,el3,head2[maxn],head3[maxn<<1]; int u_fa[maxn<<1],cnt; ll dis[maxn],w[maxn<<1],mind[maxn<<1],fa[maxn<<1][21]; priority_queue<state> pq; inline void add2(int u,int v,int w){ e2[++el2]=(edge2){v,w,head2[u]};head2[u]=el2; } inline void add3(int u,int v){ e3[++el3]=(edge2){v,0,head3[u]};head3[u]=el3; } void dijkstra(){ while(!pq.empty()) pq.pop(); memset(dis,0x3f,sizeof(dis)); dis[1]=0; pq.push((state){1,0}); while(!pq.empty()){ int u=pq.top().u;ll d=pq.top().dis;pq.pop(); if(d>dis[u]) continue; for(int i=head2[u];i;i=e2[i].nxt){ int v=e2[i].v; if(dis[u]+e2[i].w<dis[v]) pq.push((state){v,dis[v]=dis[u]+e2[i].w}); } } } int getfa(int x){ return x==u_fa[x]?x:u_fa[x]=getfa(u_fa[x]); } void kruskal(){ sort(e1+1,e1+m+1); for(int i=1;i<=n*2-1;i++) u_fa[i]=i; for(int i=1;i<=n;i++) w[i]=-1e18; cnt=n; for(int i=1;i<=m;i++){ int u=e1[i].u,v=e1[i].v; u=getfa(u);v=getfa(v); if(u==v) continue; w[++cnt]=e1[i].w; u_fa[u]=u_fa[v]=cnt; add3(cnt,u);add3(cnt,v); } } void dfs(int u){ mind[u]=u>n?1e18:dis[u]; for(int i=1;i<=20;i++) fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=head3[u];i;i=e3[i].nxt){ int v=e3[i].v; fa[v][0]=u; dfs(v); mind[u]=min(mind[u],mind[v]); } } ll calc(ll st,ll p){ for(int i=20;~i;i--) if(w[fa[st][i]]>p) st=fa[st][i]; return mind[st]; } int main(){ scanf("%d",&t); while(t--){ mem(e1);mem(e2);mem(e3);mem(head2);mem(head3);mem(w);mem(mind);mem(fa); w[lastans=el2=el3=cnt=0]=-1e18; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int u,v,w1,w2; scanf("%d%d%d%d",&u,&v,&w1,&w2); e1[i]=(edge1){u,v,w2}; add2(u,v,w1);add2(v,u,w1); } dijkstra(); kruskal(); fa[cnt][0]=0; dfs(cnt); scanf("%d%d%d",&q,&k,&s); for(int i=1;i<=q;i++){ int st,p; scanf("%d%d",&st,&p); st=(st+k*lastans-1)%n+1; p=(p+k*lastans)%(s+1); printf("%lld\n",lastans=calc(st,p)); } } }