CSP2021提高组复赛解析
前言
終于出成績了我可以寫博客辣,官方數據還沒出就先放洛谷的題目鏈接了。
正題
T1-廊橋分配
https://www.luogu.com.cn/problem/P7913
題目大意
有m1m_1m1?種一類飛機,m2m_2m2?種二類飛機,每個飛機有一個占用時間的區間。要給兩類飛機分配恰好nnn個廊橋。
如果對于一類飛機當它來到時如果有空的它這一類的廊橋就會分配給他。
求最多能容納多少飛機。
1≤n≤105,1≤m1+m2≤1051\leq n\leq 10^5,1\leq m_1+m_2\leq 10^51≤n≤105,1≤m1?+m2?≤105
解題思路
因為飛機的策略就是能停就停,我們可以考慮貪心策略。
先考慮單類的飛機,假設分配的廊橋為kkk,當一輛飛機不能進入當且僅當現在kkk個廊橋已經被霸占了,此時如果需要停靠這倆飛機就需要新開一個廊橋。
我們可以設有無數個廊橋,然后我們優先分配編號小的廊橋,然后最后如果有kkk個廊橋時答案就是在1~k1\sim k1~k的廊橋排列的飛機數。
具體的做法對于兩類各做一次,用一個優先隊列維護現在所有被霸占的廊橋的恢復時間,然后用一個set維護現在空余的廊橋編號就可以了。
時間復雜度:O(nlog?n)O(n\log n)O(nlogn)
code
#include<cstdio> #include<cstring> #include<algorithm> #include<set> #include<queue> #define mp(x,y) make_pair(x,y) using namespace std; const int N=1e5+10; struct node{int l,r; }a[N]; int n,m,f[N],g[N];set<int> s; priority_queue<pair<int,int> > q; bool cmp(node x,node y) {return x.l<y.l;} int main() {int pm;scanf("%d%d%d",&n,&m,&pm);for(int i=1;i<=max(n,max(m,pm));i++)s.insert(i);for(int i=1;i<=m;i++)scanf("%d%d",&a[i].l,&a[i].r);sort(a+1,a+1+m,cmp);for(int i=1;i<=m;i++){while(!q.empty()&&-q.top().first<=a[i].l)s.insert(q.top().second),q.pop();int x=*s.begin();f[x]++;s.erase(x);q.push(mp(-a[i].r,x));}while(!q.empty())s.insert(q.top().second),q.pop();for(int i=1;i<=n;i++)f[i]+=f[i-1];m=pm;for(int i=1;i<=m;i++)scanf("%d%d",&a[i].l,&a[i].r);sort(a+1,a+1+m,cmp);for(int i=1;i<=m;i++){while(!q.empty()&&-q.top().first<=a[i].l)s.insert(q.top().second),q.pop();int x=*s.begin();g[x]++;s.erase(x);q.push(mp(-a[i].r,x));}while(!q.empty())s.insert(q.top().second),q.pop();for(int i=1;i<=n;i++)g[i]+=g[i-1];int ans=0;for(int i=0;i<=n;i++)ans=max(ans,f[i]+g[n-i]);printf("%d\n",ans);return 0; }T2-括號序列
https://www.luogu.com.cn/problem/P7914
題目大意
一個合格的括號序被定義為
然后給出帶?,(,),?*,(,),??,(,),?的字符串,然后求有多少種把???切換成(,),?(,),*(,),?的方案使得是一個合法的括號序。
1≤k≤n≤5001\leq k\leq n\leq 5001≤k≤n≤500
解題思路
開始考慮一個一個填發現不行。
然后這個復雜度考慮區間dpdpdp,設fl,rf_{l,r}fl,r?表示區間l~rl\sim rl~r合法的方案。
然后考慮怎么轉移,先維護一個sl,rs_{l,r}sl,r?表示l~rl\sim rl~r是否能夠湊成一個長度不超過kkk的全?*?序列。
對于第111種和第333種情況,先看下l,rl,rl,r是否能是′(′'('′(′和′)′')'′)′的形式,然后第一種情況就直接加sl+1,r?1+fl+1,r?1s_{l+1,r-1}+f_{l+1,r-1}sl+1,r?1?+fl+1,r?1?(對應S/AS/AS/A),第三種我們可以枚舉k∈[l+1,r?1)k\in[l+1,r-1)k∈[l+1,r?1),然后轉移fl+1,k×sk+1,r+sl+1,k×fk+1,rf_{l+1,k}\times s_{k+1,r}+s_{l+1,k}\times f_{k+1,r}fl+1,k?×sk+1,r?+sl+1,k?×fk+1,r?(對應了AS/SAAS/SAAS/SA)就好了。
第222種情況比較麻煩,我們需要枚舉一個l≤L<R≤rl\leq L<R\leq rl≤L<R≤r然后中間填SSS,就是fl,L×fR,r×sL+1,R?1f_{l,L}\times f_{R,r}\times s_{L+1,R-1}fl,L?×fR,r?×sL+1,R?1?。但是這個枚舉比較慢,因為對于一個rrr,滿足sl,r=1s_{l,r}=1sl,r?=1的lll肯定是一個到rrr的區間,并且rrr向右移動時這個區間也向右移動,所以可以使用一個前綴和優化。
發現這樣還是過不了樣例,問題出在如果存在ASASAASASAASASA的情況,此時會被統計兩次(∣ASA∣SA|ASA|SA∣ASA∣SA和AS∣ASA∣AS|ASA|AS∣ASA∣各一次),更多的同理,所以我們可以設gl,rg_{l,r}gl,r?表示不帶情況二時的合法方案,然后在后面轉移就好了。
code
#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const ll N=510,P=1e9+7; ll n,k,f[N][N],g[N][N],S[N][N];char s[N]; signed main() {scanf("%lld%lld",&n,&k);scanf("%s",s+1);for(ll i=1;i<=n;i++){S[i][i-1]=1;for(ll j=i;j<=min(n,i+k-1);j++){S[i][j]=S[i][j-1]&(s[j]=='?'||s[j]=='*');if(!S[i][j])break;}}for(ll len=2;len<=n;len++)for(ll l=1;l<=n-len+1;l++){ll r=l+len-1;if((s[l]=='?'||s[l]=='(')&&(s[r]=='?'||s[r]==')')){(f[l][r]+=S[l+1][r-1]+f[l+1][r-1])%=P;for(ll k=l+1;k<r-1;k++)(f[l][r]+=f[l+1][k]*S[k+1][r-1]+f[k+1][r-1]*S[l+1][k])%=P;}ll sum=0,z=l;g[l][r]=f[l][r];for(ll k=l;k<r;k++){(sum+=f[l][k])%=P;while(!S[z+1][k])(sum-=f[l][z])%=P,z++;(f[l][r]+=g[k+1][r]*sum%P)%=P;}}printf("%lld\n",(f[1][n]+P)%P);return 0; }T3-回文
https://www.luogu.com.cn/problem/P7915
題目大意
有一個長度為2n2n2n的序列aaa,保證1~n1\sim n1~n都各出現了兩次,你有兩種操作
- 將aaa的開頭添加到序列bbb的末尾并在aaa移除。
- 將aaa的末尾添加到序列bbb的末尾并在aaa移除。
一操作為LLL,二操作為RRR,要求使得最終bbb回文的情況下操作序列的字典序最小。
1≤T≤100,∑n≤5×1051\leq T\leq 100,\sum n\leq 5\times 10^51≤T≤100,∑n≤5×105
解題思路
顯然第一個丟進bbb的肯定是第一個或者最后一個,我們先假設是第一個且數字為xxx,那么最后被丟進去的肯定是另一個xxx。
然后我們可以從這個xxx的位置開始作為一個區間,然后開始每次你丟進去的下一個數都必須在這個區間的左右,然后再用這個區間再擴展丟進去的數的另一個的對應位置。
但是這樣暴力搜丟左邊還是右邊是2n2^n2n的顯然不行,但是我們發現假設如果一個情況左右都能丟,在字典序最小的情況下我們肯定是先丟左邊的,而此時丟了之后不會導致右邊不能丟了,所以此時丟左邊肯定是最優的。
所以其實這樣搜是O(n)O(n)O(n)的,時間復雜度O(∑n)O(\sum n)O(∑n)。
code
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=1e6+10; int T,n,flag,a[N];char v[N]; void dfs(int d,int l,int r,int L,int R){if(l<L&&r>R){flag=1;for(int i=1;i<=2*n;i++)putchar(v[i]);putchar('\n');return;}if(d>n)return;if(L<=l&&(a[L]==a[l]&&L<l||a[L]==a[r]&&r<=R)){v[d]='L';if(a[L]==a[l]&&L<l)v[2*n-d+1]='L',dfs(d+1,l-1,r,L+1,R);else v[2*n-d+1]='R',dfs(d+1,l,r+1,L+1,R);}else if(r<=R&&(a[R]==a[l]&&L<=l||a[R]==a[r]&&r<R)){v[d]='R';if(a[R]==a[l]&&L<=l)v[2*n-d+1]='L',dfs(d+1,l-1,r,L,R-1);else v[2*n-d+1]='R',dfs(d+1,l,r+1,L,R-1);}return; } int main() {scanf("%d",&T);while(T--){scanf("%d",&n);v[2*n]='L';flag=0;for(int i=1;i<=2*n;i++)scanf("%d",&a[i]);for(int i=2;i<=2*n;i++)if(a[i]==a[1]){v[1]='L';dfs(2,i-1,i+1,2,2*n);break;}if(flag)continue;for(int i=1;i<2*n;i++)if(a[i]==a[2*n]){v[1]='R';dfs(2,i-1,i+1,1,2*n-1);break;}if(flag)continue;puts("-1");}return 0; }T4-交通規劃
https://www.luogu.com.cn/problem/P7916
題目大意
有一個nnn條水平線和mmm條垂直線交叉形成n×mn\times mn×m個格點的圖,把所有的邊按照順時針排序如圖。
每個格點之間有邊權。
TTT次詢問,每次給出線外的kkk個額外點的位置,顏色(黑白),和連接線內邊界格點的邊權。
要求給網格上的所有點染色,要求使得兩端顏色不同的邊權值和最小。
1≤T≤50,∑k≤50,2≤n,m≤5001\leq T\leq 50,\sum k\leq 50,2\leq n,m\leq 5001≤T≤50,∑k≤50,2≤n,m≤500。
解題思路
黑白染色求最小的權值其實就是為最小割,然后平面圖最小割是可以轉換成對偶圖的最短路的。
顯然的對于k=2k=2k=2的部分分就是直接求黑色額外點(如果顏色都相同顯然答案為000)左右的對偶點在對偶圖上的最短路。
對于kkk更大的情況我們具體分析一下對于下圖的情況(為了好看用了紅藍代替黑白)
我們有兩種割法(綠/黃)
發現其實可以寫成四個點相互匹配的過程,由于產生交叉的肯定不優(通過改變匹配方式使得交叉部分消去),所以匹配的貢獻可以直接寫成最短路。
然后考慮如何找到優的匹配方案,我們可以把順時針的和逆時針的匹配,因為如果順順-逆逆的匹配的話,肯定會產生交叉。(雖然這樣匹配也可能產生交叉,但是因為權值不優所以不會影響答案)
然后就是一個二分圖最大權值匹配的問題了,寫個費用流就可以了。
雖然再利用交叉性質做環形區間dpdpdp也能過,但是我不會/kk
code
#include<cstdio> #include<cstring> #include<algorithm> #include<cctype> #include<vector> #include<queue> #define ll long long #define mp(x,y) make_pair(x,y) using namespace std; const ll N=510,M=N*N,K=110; struct edge{ll to,next,w; }a[M<<2]; struct node{ll w,p,t; }q[K]; ll n,m,T,tot,ls[M],f[M],wz[N<<2]; vector<int> A,B;bool v[M]; priority_queue<pair<ll,ll> > qt; ll read(){ll x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f; } struct Netflow{struct node{ll to,next,w,c;}a[K*K*2];ll tot=1,s=1,t=2,ans,ls[K],f[K],mf[K],pre[K];bool v[K];priority_queue<int> q;void clr(){tot=1;ans=0;memset(ls,0,sizeof(ls));return;}void addl(ll x,ll y,ll w,ll c){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;a[tot].w=w;a[tot].c=c;a[++tot].to=x;a[tot].next=ls[y];ls[y]=tot;a[tot].w=0;a[tot].c=-c;return;}bool SPFA(){memset(f,0x3f,sizeof(f));q.push(s);f[s]=0;v[s]=1;mf[s]=1e9;while(!q.empty()){ll x=q.top();q.pop();v[x]=0;for(ll i=ls[x];i;i=a[i].next){ll y=a[i].to;if(a[i].w&&f[x]+a[i].c<f[y]){f[y]=f[x]+a[i].c;pre[y]=i;mf[y]=min(mf[x],a[i].w);if(!v[y])q.push(y),v[y]=1;}}}return (f[t]!=f[0]);}void Updata(){ll x=t;ans+=mf[t]*f[t];while(x!=s){a[pre[x]].w-=mf[t];a[pre[x]^1].w+=mf[t];x=a[pre[x]^1].to;}return;}ll GetAns(){while(SPFA())Updata();return ans;} }Nt; ll p(ll x,ll y) {return x*(m+1)+y;} void addl(ll x,ll y,ll w){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;a[tot].w=w;a[++tot].to=x;a[tot].next=ls[y];ls[y]=tot;a[tot].w=w;return; } void dij(ll s){memset(f,0x3f,sizeof(f));memset(v,0,sizeof(v));f[s]=0;qt.push(mp(0,s));while(!qt.empty()){ll x=qt.top().second;qt.pop();if(v[x])continue;v[x]=1;for(ll i=ls[x];i;i=a[i].next){ll y=a[i].to;if(f[x]+a[i].w<f[y]){f[y]=f[x]+a[i].w;qt.push(mp(-f[y],y));}}}return; } ll getp(ll x,ll f){if(x<=m)return p(0,x+f);if(x<=m+n)return p(x-m+f,m);if(x<=2*m+n)return p(n,m-(x-m-n+f));return p(n-(x-2*m-n+f),0); } bool cmp(node x,node y) {return x.p<y.p;} signed main() {n=read();m=read();T=read();for(ll i=1;i<n;i++)for(ll j=1;j<=m;j++){ll w=read();addl(p(i,j-1),p(i,j),w);}for(ll i=1;i<=n;i++)for(ll j=1;j<m;j++){ll w=read();addl(p(i-1,j),p(i,j),w);}for(ll i=1;i<=m;i++)addl(p(0,i-1),p(0,i),0),wz[i]=tot;for(ll i=1;i<=n;i++)addl(p(i-1,m),p(i,m),0),wz[i+m]=tot;for(ll i=1;i<=m;i++)addl(p(n,m-i+1),p(n,m-i),0),wz[i+n+m]=tot;for(ll i=1;i<=n;i++)addl(p(n-i+1,0),p(n-i,0),0),wz[i+n+2*m]=tot;while(T--){ll k=read();Nt.clr();A.clear();B.clear();for(ll i=1;i<=k;i++){q[i].w=read();q[i].p=read();q[i].t=read();a[wz[q[i].p]].w=a[wz[q[i].p]-1].w=q[i].w;}sort(q+1,q+1+k,cmp);q[0]=q[k];q[k+1]=q[1];for(ll i=1;i<=k;i++)if(q[i].t==1&&q[i-1].t==0)A.push_back(getp(q[i].p,-1));for(ll i=1;i<=k;i++)if(q[i].t==1&&q[i+1].t==0)B.push_back(getp(q[i].p,0));for(ll i=0;i<A.size();i++){Nt.addl(1,3+i,1,0);dij(A[i]);for(ll j=0;j<B.size();j++)Nt.addl(3+i,3+A.size()+j,1,f[B[j]]);}for(ll i=0;i<B.size();i++)Nt.addl(3+A.size()+i,2,1,0);printf("%lld\n",Nt.GetAns());for(ll i=1;i<=k;i++)a[wz[q[i].p]].w=a[wz[q[i].p]-1].w=0;}return 0; }總結
以上是生活随笔為你收集整理的CSP2021提高组复赛解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PC电脑版怎么玩如何正确利用电脑
- 下一篇: P7920-[Kubic]Permuta