CSP-S 2021 題解


在前面的話

其實這次比賽總體來說比去年簡單一些,可惜我考場的時候沒有 debug ,考試的時候整個人的狀態也不好,所以考試完全發揮失常

T1

題目鏈接

廊橋分配

問題解決

想要處理這道題需要引用一個結論:

  • 如果一個飛機在 \(i\) 個廊橋時占用廊橋,那么在 \(i+1\) 個廊橋時也必然占用廊橋

我們可以這樣證明理解這個結論:

因為先到的飛機是先占用廊橋的,若這個飛機在 \(i\) 的時候可以占用廊橋,也就是說這個飛機到達的時間和前一個飛離的時間是最接近的,所以多了一個廊橋時,這些飛機的相對關系不會發生變化,所以這個飛機必然能占用廊橋。

也就是說,若離這個飛機最近的飛機在 \(i+1\) 時刻占用廊橋,那么這個飛機在 \(i+1\) 時刻也可以占用廊橋

顯然,第一個到達的飛機是可以占用廊橋的 (如果廊橋數大於一)

有點像數學歸納法的感覺,總之這個結論還是很好接受的

知道了這個結論后就比較簡單了,我們可以模擬廊橋增加的過程

國內和國外分開處理

5bATs0.png

通過圖片可以很清楚的看出,第\(i\)個廊橋能接納的飛機,我們只要找到離一個區間右邊界最近的左端點就好了

其實這里的處理方法很多,\(O(n),O(nlogn)\) 的都可以過,因為我比較菜,所以寫了 \(O(nlogn)\) 的寫法

將區間左端點放入集合中,然后按右端點查找,在集合中刪去就可以了

將國內和國外分別處理完后,枚舉一次就好了

代碼實現

#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
int N,M1,M2,numa[maxn],numb[maxn],ans,q[maxn<<1];
struct air{
	int L,R;
	bool operator <(const air B)const {return this->L<B.L;}
	bool operator ==(const air B)const {return this->L==B.L&&R==B.R;}
}a[maxn],b[maxn],INF,iINF;
set<air> P;
typedef set<air>::iterator it;
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
int main(){
    N=read();M1=read();M2=read();
    for(int i=1;i<=M1;i++) a[i].L=read(),a[i].R=read(),q[++q[0]]=a[i].L,q[++q[0]]=a[i].R;
    for(int i=1;i<=M2;i++) b[i].L=read(),b[i].R=read(),q[++q[0]]=b[i].L,q[++q[0]]=b[i].R;
    sort(q+1,q+1+q[0]);sort(a+1,a+1+M1);sort(b+1,b+1+M2);
    for(int i=1;i<=M1;i++) a[i].L=lower_bound(q+1,q+1+q[0],a[i].L)-q,a[i].R=lower_bound(q+1,q+1+q[0],a[i].R)-q;
    for(int i=1;i<=M2;i++) b[i].L=lower_bound(q+1,q+1+q[0],b[i].L)-q,b[i].R=lower_bound(q+1,q+1+q[0],b[i].R)-q;
    for(int i=1;i<=M1;i++) P.insert(a[i]); INF.L=0x3f3f3f3f,INF.R=0x3f3f3f3f; P.insert(INF); iINF.L=0; iINF.R=0;
    int i=1;for(int cnt=0;cnt<M1&&!P.empty();i++){
    	it pos=P.begin();int lst=cnt;
    	while(1){
    		air now=*pos;++cnt;now.L=now.R,now.R=0x3f3f3f3f;
			P.erase(pos);pos=P.upper_bound(now);
			if((*pos)==INF)break;
		}
		numa[i]=cnt-lst+numa[i-1];
	}
	for(;i<=N;i++)numa[i]=numa[i-1];
	P.clear();
	for(int i=1;i<=M2;i++) P.insert(b[i]); P.insert(INF);
	i=1;for(int cnt=0;cnt<M2&&!P.empty();i++){
    	it pos=P.begin();int lst=cnt;
    	while(1){
    		air now=*pos;++cnt;now.L=now.R,now.R=0x3f3f3f3f;
			P.erase(pos);pos=P.upper_bound(now);
			air p=*pos;
			if((*pos)==INF)break;
		}
		numb[i]=cnt-lst+numb[i-1];
	}
	for(;i<=N;i++)numb[i]=numb[i-1];
	for(int i=0;i<=N;i++)ans=max(ans,numa[i]+numb[N-i]);
	printf("%d\n",ans);
	return 0;
}                                                   

T2

題目鏈接

括號序列

這個題一看就是區間 DP,應該沒有什么問題吧~,不知道也沒有其他做法,反正我只能看出是區間 DP

就存在如何定義的問題

很顯然的,我們可以定義 \(F[i][j]\) 表示從 \(i\)\(j\) 的合法方案數

考慮轉移,我們發現無非就是幾種情況,按照題目的意思寫即可

實現我們為了判定一個串是否是 * 的一段,也就是 \(S\) 需要構造一個 \(sum[i]\) 表示 \(i\) 之前的 * 和 ? 的個數,查找一段的時候相減即可

  1. ()/(S) 這兩種本質上是相同的,只要判斷 \(i\)\(j\) 內部是否都是 * ,若是的則\(F[i][j]++\)

  2. (AS) \(F[i][j]+=F[i+1][k]\)\(k\) 是內部區間的右端點,要保證從 \(k\)\(j\) 都是 * 方可轉移

  3. (SA) 和 (AS) 相同 \(F[i][j]+=F[k][j-1]\)\(k\) 是內部區間的左端點,要保證從 \(i\)\(k\) 都是 * 方可轉移

  4. (A) 不解釋了 \(F[i][j]+=F[i+1][j-1]\) 這個不理解的回爐重造吧

  5. AB/ASB 這個時最難的,我們需要枚舉內部兩個區間的斷點,設\(k1\) 表示內部左區間的右端點,\(k2\) 表示右邊區間的右端點,顯然需要從 \(k1\)\(k2\) 都是 * 方可轉移,\(F[i][j]+=F[i][k1]\times F[k2][j]\)

這里我們發現自己已經寫了個 \(O(n^4)\)的DP了,其實還有一些小問題,對於 AB/ASB這種情況會算重

例如 ()()() 這組數據計算 AB/ASB 時被算了兩次,一次()+()(),一次 ()()+(),仔細想想是不是

所以我們需要從問題的源頭入手,是不是定義除了問題,考慮再加入一個定義:\(G[i][j]\) 表示左端點和右端點必須是一對配對的括號的方案數,例如:(()())是合法的,(())()是不合法的,這個定義很重要

我們發現()()+()這次計算是沒有必要的,所以要把它除去,需要修改 5 的轉移方程,變成 \(F[i][j]+=G[i][k1]\times F[k2][j]\),就可以將重復去掉

考慮 \(G[i][j]\) 如何得到,發現對於 1,2,3,4 的情況 \(F\)\(G\) 的轉移完全相同,就是 5 的情況是不可能得到 \(G\)

好了現在我們的代碼已經可以跑 \(n=100\) 左右的數據了,考慮如何優化

瓶頸卡在 5 的轉移:\(F[i][j]+=G[i][k1]\times F[k2][j]\),每個\(k1\) 都要乘上 \(\sum_{\text{合法的k2}} F[k2][j]\) ,顯然 \(k2\) 是連續一段的,很容易想到構造一個前綴和,每次只需要乘一次就可以了

此時時間復雜度已經優化到了 \(O(n^3)\) 可以過此題了

代碼實現

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=505;
const LL TT=1e9+7;
int N,K,nxt[maxn];
LL sum[maxn],F[maxn][maxn],G[maxn][maxn],S[maxn];
char s[maxn];
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
inline LL min(LL a,LL b){return a<b?a:b;}
inline LL max(LL a,LL b){return a>b?a:b;}
int main(){
	N=read();K=read();int q=0;
	scanf("%s",s+1);
	for(int i=1;i<=N;i++) sum[i]=sum[i-1]+(s[i]=='*'||s[i]=='?');
	nxt[N]=N;for(int i=N-1;i;i--) nxt[i]=(s[i]=='*'||s[i]=='?')?nxt[i+1]:i; 
	for(int i,j,k,k1,k2,L=1;L<N;L++)
	for(i=1;i+L<=N;i++) if(s[i]!='*'&&s[i]!=')'){
		j=i+L;if(s[j]=='('||s[j]=='*')continue;
		(j-i-1==sum[j-1]-sum[i]&&j-i-1<=K)&&((G[i][j]=(F[i][j]+1)%TT)|(F[i][j]=(F[i][j]+1)%TT),0);
		if(j-i-1<2)continue;
		F[i][j]=(F[i][j]+F[i+1][j-1])%TT; G[i][j]=(G[i][j]+F[i+1][j-1])%TT;
		for(k=2;k-1<=K&&i+k<j-1;k++) sum[i+k-1]-sum[i]==k-1&&((F[i][j]=(F[i][j]+F[i+k][j-1])%TT)|(G[i][j]=(G[i][j]+F[i+k][j-1])%TT),0);
		for(k=2;k-1<=K&&i+1<j-k;k++) sum[j-1]-sum[j-k]==k-1&&((F[i][j]=(F[i][j]+F[i+1][j-k])%TT)|(G[i][j]=(G[i][j]+F[i+1][j-k])%TT),0);
		S[i+1]=0;for(k=i+2;k<j;k++) S[k]=(S[k-1]+F[k][j])%TT;
		for(k1=i+1;k1<j-1;k1++) F[i][j]=(F[i][j]+G[i][k1]*(S[min(j-1,min(nxt[k1+1],k1+K+1))]-S[k1]+TT)%TT)%TT;
//		for(k1=i+1;k1<j-1;k1++)
//		for(k2=k1+1;k2<j&&k2-k1-1<=K;k2++){
//			(sum[k2-1]-sum[k1]==k2-k1-1)&&(F[i][j]=(F[i][j]+G[i][k1]*F[k2][j]%TT)%TT,0);
//		}
	}
	printf("%lld\n",F[1][N]);
	return 0;
}

T3

題目描述

回文

這道題的做法也很多,我主要講我看到最簡單的一種,來自於zzj

我們先觀察樣例,很容易的能得出一個結論:對於一個串來說,外面有\(n\)個是在回文串前面的,里面的\(n\)個是在回文串后面的

在樣例中,藍色的表示前面的回文,綠色的表示后面的回文,藍色在外面,綠色在里面,和我們的結論一樣,其實結論也挺顯然的

所以我們要找出一個在外部和在內部的點模擬就好了

顯然的,第一個點肯定是最左邊和最右邊的任意一個,我們假設最左邊的點為第一個被取走的點,它肯定在藍色的部分,這個數對應的另外一個位置肯定在綠色的部分,於是,我們就可以模擬了

思考,如何模擬匹配的過程

先設四個指針 \(L1,L2,R1,R2\) 表示左邊藍色的右端點,綠色的左端點,綠色的右端點,右邊藍色的右端點,此時的綠色和藍色的交接位置是不確定的,我們就要模擬出來

觀察串是怎么匹配的,在前面的回文上一個位置上為 \(L\) ,那么從左邊取,則,\(L1++\),若從右邊,則 \(R2--\) 。在后邊的回文上的一個位置上為 \(L\)\(L2++\) ,若從右邊,則 \(R1--\)

考慮到前面和后面是一樣的,我們考慮一起處理,也就是第 \(i\) 位和第 \(N\times 2-i+1\) 位一起處理,我們采用一種逆向思維還原匹配過程,我們就是要找藍色和綠色交界的位置

  • \(s[L1]=s[L2]\) 則前面的串從左邊取,后面的串也從左邊取,\(L1++,L2--\)
    (此時為什么不是 \(L2++\),因為我們反向模擬匹配的過程,可以看出還原后綠色向右邊移動了一格,也就代表着這個是在后面的回文中出現的,這個思想很重要),這個時候

  • \(s[L1]=s[R1]\) 則前面的串從左邊取,后面的串從右邊取,\(L1++,R1++\)

  • \(s[L2]=s[R2]\) 則前面的串從右邊取,后面的串從左邊取,\(L2--,R2--\)

  • \(s[R1]=s[R2]\) 則前面的串從右邊取,后面的串從右邊取,\(R1++,R2--\)

  • 若四種方式都不可以匹配,則無解,也挺顯然的

我們在匹配的時候已經處理好取的順序了,輸出即可,當然要注意一個小細節,也就是取完后 \(L1\) 會大於 \(L2\) 或者 \(R1\) 大於 \(R2\) ,顯然不可能取的。

題目中還需要字典序最小,我們只需要按照 1,2,3,4,的順序判斷能不能取,肯定是最小的,因為 \({L,L}<L,R<R,L<R,R\)

現在已經解決了一遍的問題了,若第一個匹配的數字為右邊的,那么同理即可,這里留給大家自己思考

代碼實現

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e6+5;
int N,L1,L2,R1,R2,ans[maxn],a[maxn],ok,T;
char ch[3]="LR";
struct IO{
    static const int S=1<<21;
    char buf[S],*p1,*p2;int st[105],Top;
    ~IO(){clear();}
    inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
    inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
    inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
    inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='r');return *this;}
    template<typename T>inline IO&operator >> (T&x){
        x=0;bool f=0;char ch=gc();
        while(ch<'0'||ch>'9'){if(ch=='-') f^=1;ch=gc();}
        while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=gc();
        f?x=-x:0;return *this;
    }
    inline IO&operator << (const char c){pc(c);return *this;}
    template<typename T>inline IO&operator << (T x){
        if(x<0) pc('-'),x=-x;
        do{st[++st[0]]=x%10,x/=10;}while(x);
        while(st[0]) pc('0'+st[st[0]--]);return *this;
    }
}fin,fout;
int main(){
	fin>>T;
	while(T--){
		fin>>N;N<<=1;ok=0;
		for(int i=1;i<=N;i++) fin>>a[i];	
		for(int i=2;i<=N;i++) if(a[i]==a[1]){L2=i-1;R1=i+1;break;}; L1=2;R2=N;
		ans[1]=0;ans[N]=0; 
		for(int i=2;i<=(N>>1);i++) {
			if(L1<L2&&a[L1]==a[L2]) L1++,L2--,ans[i]=0,ans[N-i+1]=0;
			else if(L1<=L2&&R1<=R2&&a[L1]==a[R1]) L1++,R1++,ans[i]=0,ans[N-i+1]=1;
			else if(L1<=L2&&R1<=R2&&a[L2]==a[R2]) L2--,R2--,ans[i]=1,ans[N-i+1]=0;
			else if(R1<R2&&a[R1]==a[R2]) R1++,R2--,ans[i]=1,ans[N-i+1]=1;
			else {ok=1;break;}
		}
		if(ok==0) {for(int i=1;i<=N;i++)putchar(ch[ans[i]]);putchar('\n');continue;}
		ok=0;
		for(int i=1;i<N;i++) if(a[i]==a[N]){L2=i-1;R1=i+1;break;}; L1=1;R2=N-1;
		ans[1]=1;ans[N]=0; 
		for(int i=2;i<=(N>>1);i++) {
			if(L1<L2&&a[L1]==a[L2]) L1++,L2--,ans[i]=0,ans[N-i+1]=0;
			else if(L1<=L2&&R1<=R2&&a[L1]==a[R1]) L1++,R1++,ans[i]=0,ans[N-i+1]=1; 
			else if(L1<=L2&&R1<=R2&&a[L2]==a[R2]) L2--,R2--,ans[i]=1,ans[N-i+1]=0;
			else if(R1<R2&&a[R1]==a[R2]) R1++,R2--,ans[i]=1,ans[N-i+1]=1;
			else {ok=1;break;}
		}
		if(ok==0) {for(int i=1;i<=N;i++)putchar(ch[ans[i]]);putchar('\n');continue;}
		putchar('-');putchar('1');putchar('\n');
	}
	return 0;
}

T4

題目鏈接

交通規划

此題本人只會寫 \(k=2\) 時的點,此想法來自於 ix35,%%%,我把它描述的清楚一點

非常顯然,當 \(k=2\) 的時候,這是一個最小割問題,不知道的建議右轉百度

而這又是一個對偶圖問題,關於對偶圖的最大流問題可以轉化成最短路,這是一種常用套路,推薦一道類似的題目 :狼抓兔子

把一個空看成一個節點,把邊看成空和空之間的邊,重新建圖

在轉化后,從綠色節點到藍色節點的距離就是這個圖中的最小割

可以怎樣理解這個問題,若有一條路徑綠色節點和藍色節點聯通,那么這條路徑可以將這個圖分成兩部分,黑白兩點分別在兩個部分里面

那么 \(k=2\) 的點就解決了

\(k!=2\) 的時候,我們也可以采用同樣的方法,順時針看,從黑到白的看成藍點,從白到黑的看成綠點

我們可以把一排的綠點看成一個超級綠點,藍點同理,因為一排相同顏色點是等價的,所以綠藍點時交錯出現的,設 \(cnt\) 為塊的個數

一條線可以划分出兩個區間,將兩個區域分開,我們為了使得總的代價最小,肯定希望一條線能划開多個區間或者不要交錯,因為交錯了的代價肯定更劣

可以感性用上的這張圖來理解,但是實際上和這個題目還是有點差異的

我們可以用 \(dij\) 或者 \(spfa\)(它已經死了) 來求出 \(g[i][j]\) 表示從第 \(i\) 個塊和第 \(j\) 個塊分離的最小代價

考慮一個塊,必然要和其他不同顏色的塊分開,也就是說要單獨在一個區域內

觀察發現 \(k\) 非常小,就可以使用 \(DP\) 來枚舉每種情況,定義 \(F[i][j]\) 表示從第 \(i\) 個串到第 \(j\) 個串能完全匹配的最小代價,初始肯定是 \(F[i][i+1]=g[i][i+1]\)

分為兩種情況

  • \(i,j\) 分成兩部分分開匹配,也就是 \(F[i][j]=F[i][k]+F[k+1][j]\)

  • \(i,j\) 兩個點匹配,\(F[i][j]=F[i+1][j-1]+g[i][j]\)

最后的答案也就是 \(F[1][cnt]\)

單這里還有一個小問題,就是這個圖是一個環,我們要拆成鏈處理,轉移方程都不變,就是處理答案的時候需要掃一遍 \(F[i][i+cnt-1]\) 的最大值即可

代碼里面細節巨多,我在實現的時候沒有每次去建圖,而是用了 \(vector\) 來刪掉重復的邊,沒有設超級綠點和超級藍點,用了修改邊后的第一個點來作為基准點,因為基准點到這一塊所有點的距離都是 \(0\),所以直接拿基准點作為超級綠點/藍點就可以了

其他細節看代碼吧,時間復雜度 \(O(T\times (k^3+knmlognm))\)

代碼實現

#pragma GCC optimize(2)
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
int N,M,T,K;
int mp[2][505][505],line[2005][3],node[2005][2],id[2005],G[2005][2005],dis[260105],vis[260105],cnt,F[2005][2005],ans;
struct edge{
	int x,w;
	bool operator <(const edge B)const {return w<B.w;}
}hep[260105];
int hep_len;
vector<edge> e[260105];
inline void add_e(int x,int y,int z){
	e[x].push_back((edge){y,z});
}
inline void add_edge(int x,int y,int z){
	e[x].push_back((edge){y,z});e[y].push_back((edge){x,z});
}
struct IO{
    static const int S=1<<21;
    char buf[S],*p1,*p2;int st[105],Top;
    ~IO(){clear();}
    inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
    inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
    inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
    inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='r');return *this;}
    template<typename T>inline IO&operator >> (T&x){
        x=0;bool f=0;char ch=gc();
        while(ch<'0'||ch>'9'){if(ch=='-') f^=1;ch=gc();}
        while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=gc();
        f?x=-x:0;return *this;
    }
    inline IO&operator << (const char c){pc(c);return *this;}
    template<typename T>inline IO&operator << (T x){
        if(x<0) pc('-'),x=-x;
        do{st[++st[0]]=x%10,x/=10;}while(x);
        while(st[0]) pc('0'+st[st[0]--]);return *this;
    }
}fin,fout;
inline int calc(int x,int y){return (x-1)*(M-1)+y;}
bool cmp(int x,int y){return line[x][0]<line[y][0];}
int Nxt(int x){
	return x==(N+M<<1)?1:x+1;
}
void update_edge(int x,int y,int z){
	int flg=0;
	vector<edge>::iterator it=e[x].begin();
	for(;it!=e[x].end();++it){
		if(it->x==y){
			if(it->w==z){flg=1;break;}		
			else {e[x].erase(it);break;}
		}
	}
	if(!flg)add_e(x,y,z);
	it=e[y].begin();flg=0;
	for(;it!=e[y].end();++it){
		if(it->x==x){
			if(it->w==z){flg=1;break;}
			else {e[y].erase(it);break;}
		}
	}
	if(!flg)add_e(y,x,z);
	return ;
}
void put(edge x){
	hep[++hep_len]=x;int son=hep_len;
	while(son>1&&hep[son]<hep[son>>1]) swap(hep[son],hep[son>>1]),son>>=1;
	return ;
}
edge get(){
	edge ret=hep[1];int son,fa=1;hep[1]=hep[hep_len--];
	while((fa<<1)<=hep_len){
		if((fa<<1)>hep_len||hep[fa<<1]<hep[fa<<1|1]) son=fa<<1;else son=fa<<1|1;
		if(hep[son]<hep[fa]) swap(hep[son],hep[fa]),fa=son;else break;
	}
	return ret;
}
void dij(int s){
	memset(dis,63,sizeof dis);
	memset(vis,0,sizeof vis);
	dis[s]=0;edge now;now.x=s;now.w=0;hep_len=0;
	put(now);
	while(hep_len){
		edge tmp=get();
		int x=tmp.x;
		if(vis[x])continue;vis[x]=1;
		for(int i=0,sz=e[x].size();i<sz;++i)
			if(!vis[e[x][i].x]&&dis[e[x][i].x]>dis[x]+e[x][i].w){
				dis[e[x][i].x]=dis[x]+e[x][i].w;
				now.x=e[x][i].x;now.w=dis[e[x][i].x];
				put(now);
			}
	}
	return ;
}
int main(){
	freopen("traffic.in","r",stdin);
	freopen("traffic.out","w",stdout);
	fin>>N>>M>>T;
	for(int i=1;i<N;i++)
	for(int j=1;j<=M;j++) fin>>mp[0][i][j];
	for(int i=1;i<=N;i++)
	for(int j=1;j<M;j++) fin>>mp[1][i][j];
	for(int i=1;i<N;i++)
	for(int j=1;j<M;j++){
		if(i!=N-1){add_edge(calc(i,j),calc(i+1,j),mp[1][i+1][j]);}
		if(j!=M-1){add_edge(calc(i,j),calc(i,j+1),mp[0][i][j+1]);}
	}
	for(int i=1;i<N;i++){
		add_edge(calc(i,1),(N-1)+(M-1<<1)+4+(N-1-i+1)+(N-1)*(M-1),mp[0][i][1]);
		add_edge(calc(i,M-1),(M-1)+2+(i)+(N-1)*(M-1),mp[0][i][M]);
	}
	for(int j=1;j<M;j++){
		add_edge(calc(1,j),j+1+(N-1)*(M-1),mp[1][1][j]);
		add_edge(calc(N-1,j),(N-1)+(M-1)+3+(M-1-j+1)+(N-1)*(M-1),mp[1][N][j]);
	}
	while(T--){
		fin>>K;
		memset(G,63,sizeof G);memset(F,63,sizeof F);ans=0x3f3f3f3f;cnt=0;
		for(int i=1;i<=K;i++) {int l,x,c;fin>>l>>x>>c;line[i][0]=x;line[i][1]=c;id[i]=i;line[i][2]=l;}
		if(K<=1){fout<<0<<'\n';continue;}
		sort(id+1,id+1+K,cmp);
		for(int i=1;i<=K;i++){
			int nx=i+1<=K?i+1:1;
			if(line[id[i]][1]==line[id[nx]][1]){
				for(int j=Nxt(line[id[i]][0]);j!=line[id[nx]][0];j=Nxt(j))update_edge(j+(N-1)*(M-1),Nxt(j)+(N-1)*(M-1),0);
				update_edge(line[id[nx]][0]+(N-1)*(M-1),Nxt(line[id[nx]][0])+(N-1)*(M-1),line[id[nx]][2]);
			}
			else{
				cnt++;
				node[cnt][0]=Nxt(line[id[i]][0])+(N-1)*(M-1);node[cnt][1]=line[id[i]][1];
				for(int j=Nxt(line[id[i]][0]);j!=line[id[nx]][0];j=Nxt(j))update_edge(j+(N-1)*(M-1),Nxt(j)+(N-1)*(M-1),0);
				update_edge(line[id[nx]][0]+(N-1)*(M-1),Nxt(line[id[nx]][0])+(N-1)*(M-1),line[id[nx]][2]);
			}
		}
		for(int i=1;i<=cnt;i++) {
			dij(node[i][0]);
			for(int j=1;j<=cnt;j++) if(i!=j) G[i][j]=min(G[i][j],dis[node[j][0]]);
		}
		for(int i=1;i<=cnt;i++)for(int j=1;j<=cnt;j++) G[i+cnt][j]=G[i][j+cnt]=G[i+cnt][j+cnt]=G[i][j];
		for(int i=1;i<(cnt<<1);i++) F[i][i+1]=G[i][i+1];
		for(int L=3;L<cnt;L+=2)
			for(int i=1,j;i+L<=cnt;i++){
				j=i+L;
				for(int k=i+1;k<=j;k+=2)F[i][j]=min(F[i][j],F[i][k]+F[k+1][j]);
				F[i][j]=min(F[i][j],G[i][j]+F[i+1][j-1]);
			}
		for(int i=1;i<=cnt;i++) ans=min(ans,F[i][i+cnt-1]);
		fout<<(ans==0x3f3f3f3f?0:ans)<<'\n';
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM