題目鏈接
題意
-
就是有一個長度爲的只含有和的數組,開始你不知道數組的內容,給你兩種描述:
- 第一種表示區間內至少含有個
- 第二種表示除去區間的剩下部分至少含有個
然後讓你求在滿足所有這些給定的限制條件下,整個數組最少含有多少個
題解
- 首先這個題和17CCPC哈爾濱站L有相似之處,只是把樹上問題放到了序列上,問題就變複雜了一些
- 爲什麼變複雜了呢?首先那個題的題解鏈接先貼上:17CCPC哈爾濱站題解,可以看到爲什麼那個題可以,原因就在於那個題實際上是利用了區間包含的特點,而沒有區間交叉的情況,因爲當前節點的子樹一定包含所有以兒子爲根節點的子樹,所以只有包含關係,當放到序列上時,就會產生交叉情況
- 另外如果你做過POJ3169的話,那麼這個題應該能根塊想出來做法
- 首先容易想到的是如果答案是,只要那麼也一定可以滿足給定的所有條件,因爲你可以在任意一個空的位置放上一個,所以答案具有單調性,考慮二分,關鍵是怎麼的問題,考慮前綴和,首先對於第一種描述,可以轉化爲,那麼對於第二種描述,考慮轉化一下:區間內的1的個數最多有個,即,然後還有一些隱含的條件那就是,以及,根據這些約束條件建圖(具體建圖方法參見挑戰),然後跑判斷是否有負環,如果有則差分約束系統無解,即二分的太小
- 另外有一個剪枝技巧就是如果跑中途遇到有某個節點的值小於零,那麼一定有負環,原因是由於,節點向節點連了一條去哪治爲0的邊,那麼就是說對於任意一個節點到節點0都有一條最長是0的長度的最短路,那麼如果0到某個節點的值小於就一定有負環
- 假了這個優化只用了就跑完了,目前這份代碼的提交在Codeforces的提交是所有提交當中跑的最快的(其中_TLE_和wzw19991105都是我的號)的提交用的是C++ STL的,是自己手寫了個
剪枝前是這樣的(差一點沒過去)
代碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=3005;
const int maxm=100005;
int n,m1,m2,head[maxn],tot,dis[maxn],inq[maxn],cnt[maxn];
struct opt{int l,r,w;}a[maxn],b[maxn];
struct edge{int u,v,w,type,next;}e[maxm];
void add_edge(int u,int v,int w,int t) {e[++tot]=edge{u,v,w,t,head[u]};head[u]=tot;}
struct dequeue_{
int a[maxn],L,R,siz=0;
bool empty() {return siz==0;}
dequeue_(int l=1,int r=0,int siz_=0) {L=l,R=r,siz=siz_;}
void clear() {
L=1,R=0,siz=0;
}
bool push_front(int val) {
if(siz==maxn-1) return false;
if(L==1) L=maxn-1,a[L]=val,siz++;
else L--,a[L]=val,siz++;
return true;
}
bool push_back(int val) {
if(siz==maxn-1) return false;
if(R==maxn-1) R=1,a[R]=val,siz++;
else R++,a[R]=val,siz++;
return true;
}
int pop_front() {
if(siz==0) return 0;
int ans=0;
if(L==maxn-1) ans=a[L],L=1,siz--;
else ans=a[L],L++,siz--;
return ans;
}
int pop_back() {
if(siz==0) return 0;
int ans=0;
if(R==1) ans=a[R],R=maxn-1,siz--;
else ans=a[R],R--,siz--;
return ans;
}
int front() {return a[L];}
int back() {return a[R];}
};
bool spfa(int s,int mid) {
for(int i=0;i<=n;i++) dis[i]=0x3f3f3f3f,inq[i]=0,cnt[i]=0;
dequeue_ que;
que.push_front(s);
inq[s]=1,dis[s]=0,cnt[0]=1;
while(!que.empty()) {
int cur=que.front();
que.pop_front();
inq[cur]=0;
for(int i=head[cur];i;i=e[i].next) {
int weight=e[i].type?mid+e[i].w:e[i].w;
if(dis[e[i].v] > dis[e[i].u]+weight) {
dis[e[i].v]= dis[e[i].u]+weight;
if(dis[e[i].v] < 0) return false; //根據建圖特點剪枝
if(!inq[e[i].v]) {
if(!que.empty() && dis[e[i].v]>=dis[que.front()]) que.push_back(e[i].v);
else que.push_front(e[i].v); //SLF優化
cnt[e[i].v]++,inq[e[i].v]=1;
if(cnt[e[i].v]>n) return false;
}
}
}
}
return true;
}
bool check(int mid) {
e[tot].w=-mid,e[tot-1].w=mid; //修改這兩條邊的權值
return spfa(0,mid);
}
int main() {
int t;scanf("%d",&t);
while(t--) {
scanf("%d %d %d",&n,&m1,&m2);
for(int i=1;i<=m1;i++) scanf("%d %d %d",&a[i].l,&a[i].r,&a[i].w);
for(int i=1;i<=m2;i++) scanf("%d %d %d",&b[i].l,&b[i].r,&b[i].w);
for(int i=1;i<=n;i++) add_edge(i,i-1,0,0),add_edge(i-1,i,1,0);
for(int i=1;i<=m1;i++) add_edge(a[i].r,a[i].l-1,-a[i].w,0);
for(int i=1;i<=m2;i++) add_edge(b[i].l-1,b[i].r,-b[i].w,1);
add_edge(0,n,0,0),add_edge(n,0,0,0);
int l=0,r=n,ans=-1; //二分答案
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
printf("%d\n",ans);
for(int i=0;i<=n;i++) head[i]=0; //清空
tot=0;
}
}