CF習題集二

liuchanglc發表於2020-08-03

CF習題集二

一、CF507E Breaking Good

題目描述

\(Breaking Good\)這個遊戲對於有經驗的玩家來說也有一定的難度。

遊戲的主角小明希望加入一個叫斧頭幫的犯罪團伙。這個團伙控制著整個國家\(n\)個城市間的\(m\)條雙向道路,這些道路保證沒有自環和重邊,任何城市可以通過這些道路到達任何其他城市。

然而道路並不全都能通行,有些道路是需要修復。

現在這個團伙要搞一個大新聞!搞事地點位於城市\(1\)。像往常一樣,這個行動最難的部分是搞事後如何逃到他們在城市n的總部。為了獲得該團伙的信任,小明決定負責這項搞事行動,而且他提出了一個看起來很明智的計劃。

首先,他們將在從城市1返回途中使用的路徑總長度必須儘可能短;然後,為了讓搞的大新聞更加刺激,他們必須炸燬所有不在這條路徑上的其他道路。但是他們不必炸掉不能通行的道路。

如果選擇的道路有一些不能通行的道路,他們將不得不在行動之前修復那些道路。

小明發現,有很多路徑滿足了條件\(1\)(即儘可能短),所以他決定在其中選擇一條路徑,使受影響道路的總數最小化。

你能幫助小明完成搞事並獲得該團伙的信任嗎?

分析

首先,我們要選擇一條最短的路徑

在路徑最短的基礎上,我們要儘量使受影響的道路數更少

因此我們要在跑\(Dij\)的結構體裡儲存三個東西

即當前節點的編號,當前節點距離起點的最短路徑,當前路徑下更改的道路條數

在進行鬆弛操作時,如果\(dis[u]>dis[now]+b[i].val\)

那麼我們像之前那樣更新\(dis\)值即可

如果\(dis[u]=dis[now]+b[i].val\)但是新的路徑更改的道路條數更少

此時我們也需要更新

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int head[maxn],tot=1;
struct asd{
    int from,to,next,val,jud;
}b[maxn];
void ad(int aa,int bb,int cc,int dd){
    b[tot].from=aa;
    b[tot].to=bb;
    b[tot].next=head[aa];
    b[tot].val=cc;
    b[tot].jud=dd;
    head[aa]=tot++;
}
struct jie{
    int num,jl,hf;
    jie(int aa,int bb,int cc){
        num=aa,jl=bb,hf=cc;
    }
    bool operator < (const jie& A) const{
        if(jl==A.jl) return hf>A.hf;
        return jl>A.jl;
    }
};
priority_queue<jie> q;
bool viss[maxn];
int dis[maxn],jl[maxn],hf[maxn];
void dij(){
    memset(dis,0x3f,sizeof(dis));
    memset(hf,0x3f,sizeof(hf));
    dis[1]=0;
    hf[1]=0;
    q.push(jie(1,0,0));
    while(!q.empty()){
        int now=q.top().num;
        int nhf=q.top().hf;
        q.pop();
        if(viss[now]) continue;
        viss[now]=1;
        for(int i=head[now];i!=-1;i=b[i].next){
            int u=b[i].to;
            if(dis[u]>dis[now]+b[i].val){
                dis[u]=dis[now]+b[i].val;
                jl[u]=i;
                hf[u]=nhf+b[i].jud;
                q.push(jie(u,dis[u],hf[u]));
            } else if(dis[u]==dis[now]+b[i].val){
                if(hf[u]>nhf+b[i].jud){
                    jl[u]=i;
                    hf[u]=nhf+b[i].jud;
                    q.push(jie(u,dis[u],hf[u]));
                }
            }
        }
    }
}
vector<int> g;
bool vis[maxn];
int main(){
    memset(head,-1,sizeof(head));
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int aa,bb,cc;
        scanf("%d%d%d",&aa,&bb,&cc);
        ad(aa,bb,1,cc^1);
        ad(bb,aa,1,cc^1);
    }
    dij();
    int ans=0;
    int now=n;
    while(jl[now]){
        if(jl[now]%2==0) g.push_back(jl[now]-1);
        else g.push_back(jl[now]);
        now=b[jl[now]].from;
    }
    for(int i=0;i<g.size();i++){
        vis[g[i]]=1;
    }
    for(int i=1;i<tot;i+=2){
        if(vis[i]){
            if(b[i].jud==1){
                ans++;
            }
        } else {
            if(b[i].jud==0){
                ans++;
            }
        }
    }
    printf("%d\n",ans);
    for(int i=1;i<tot;i+=2){
        if(vis[i]){
            if(b[i].jud==1){
                printf("%d %d %d\n",b[i].from,b[i].to,1);
            } 
        } else {
            if(b[i].jud==0){
                ans++;
                printf("%d %d %d\n",b[i].from,b[i].to,0);
            }
        }
    }
    return 0;
}

二、CF467C George and Job

題目描述

新款手機 \(iTone6\) 近期上市,\(George\) 很想買一隻。不幸地,\(George\) 沒有足夠的錢,所以 \(George\) 打算當一名程式猿去打工。現在\(George\)遇到了一個問題。 給出一組有 \(n\) 個整數的數列\(p_1,p_2,...,p_n\),你需要挑出 \(k\) 組長度為 \(m\) 的數,要求這些數互不重疊 即$ [l_{1},r_{1}],[l_{2},r_{2}],...,[l_{k},r_{k}] (1<=l_{1}<=r_{1}<l_{2}<=r_{2}<...<l_{k}<=r_{k}<=n;r_{i}-l_{i}+1=m)[l1​,r1​],[l2​,r2​],...,[lk​,rk​]$

使選出的數的和值最大,請你幫助George碼出這份程式碼

分析

我們設\(f[i][j]\)為前\(i\)個數選出了\(j\)組,其中第\(i\)個數必須選的最大值

那麼我們就可以寫出如下的狀態轉移方程

f[i][k]=max(f[i][k],f[j][k-1]+sum[i]-sum[i-m]);

時間複雜度為\(O(n^3)\)

實際上,我們可以用單調佇列對於每一個\(i\)維護\(f[j][k-1]\)的最大值

這樣時間複雜度就降到了\(O(n^2)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e3+5;
#define int long long
int sum[maxn],a[maxn],f[maxn][maxn],q[maxn],head,tail;
signed main(){
    int n,m,p;
    scanf("%lld%lld%lld",&n,&m,&p);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    int ans=0;
    for(int j=1;j<=p;j++){
        head=1,tail=1;
        memset(q,0,sizeof(q));
        for(int i=m;i<=n;i++){
            if(head<=tail)f[i][j]=max(f[i][j],f[q[head]][j-1]+sum[i]-sum[i-m]);
            while(head<=tail && f[i-m+1][j-1]>f[q[tail]][j-1]) tail--;
            q[++tail]=i-m+1;
            ans=max(ans,f[i][p]);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

三、CF333E Summer Earnings

題目描述

在一個平面內給出\(n\)個點的座標,任選其中三個為圓心作半徑相同的圓,要求這三個圓不能相交但可以相切,求能畫出的圓中的最大半徑。

分析

對於平面上的三個點,我們可以將其分為兩種情況

一種情況是三個點都位於一條直線上

此時我們的最大直徑只能是三點當中距離最小的兩點的距離

還有一種情況是三個點不在同一條直線上

此時我們的最大直徑也只能是三點當中距離最小的兩點的距離

因為我們要保證圓只能相切,不能相交

如果直徑再大一點,勢必會出現相交的情況

所以我們可以先預處理出任意兩點間的距離,然後按距離從小到大排好序

每次取出兩個點,我們就判斷一下它們是否和同一個點已經連到一起

如果已經連到一起,我們就輸出當前答案,否則繼續尋找

而判斷兩個點是否和同一個點連到一個我們可以用\(bitset\)解決

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef double dd;
const int maxn=3005;
dd jlx[maxn],jly[maxn];
bitset<maxn> g[maxn];
struct asd{
    int from,to;
    dd da;
}b[maxn*maxn];
dd solve(int aa,int bb){
    return (dd)sqrt((jlx[aa]-jlx[bb])*(jlx[aa]-jlx[bb])+(jly[aa]-jly[bb])*(jly[aa]-jly[bb]));
}
bool cmp(asd aa,asd bb){
    return aa.da>bb.da;
}
int main(){
    int n,cnt=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf",&jlx[i],&jly[i]);
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(i==j) continue;
            b[++cnt].da=solve(i,j);
            b[cnt].from=i;
            b[cnt].to=j;
        }
    }
    sort(b+1,b+1+cnt,cmp);
    for(int i=1;i<=cnt;i++){
        int aa=b[i].from,bb=b[i].to;
        g[aa][bb]=1,g[bb][aa]=1;
        if((g[aa] & g[bb]).count()){
            printf("%.20lf\n",b[i].da/2.0);
            exit(0);
        }
    }
    return 0;
}

四、CF132C Logo Turtle

題目描述

很多人把\(LOGO\)程式語言和海龜圖形聯絡起來。在這種情況下,海龜沿著直線移動,接受命令“T”(“轉向180度”)和“F”(“向前移動1單元”)。

你會收到一份給海龜的命令清單。你必須從列表中精確地改變N個命令(一個命令可以被改變多次)。要求出海龜在遵循修改後的所有命令後,會從起點最遠可以移到多遠?

分析

傳送門

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
char s[maxn];
int f[maxn][maxn][3],wz[maxn];
int main(){
	for(int i=0;i<maxn;i++){
		for(int j=0;j<maxn;j++){
			f[i][j][1]=f[i][j][0]=-0x3f3f3f3f;
		}
	}
	scanf("%s",s+1);
	int n;
	scanf("%d",&n);
	int len=strlen(s+1);
	f[0][0][0]=0;
	f[0][0][1]=0;
	for(int i=1;i<=len;i++){
		for(int j=0;j<=n;j++){
			for(int k=0;k<=j;k++){
				if(s[i]=='F'){
					if(k&1){
						f[i][j][0]=max(f[i][j][0],f[i-1][j-k][1]);
						f[i][j][1]=max(f[i][j][1],f[i-1][j-k][0]);
					} else {
						f[i][j][0]=max(f[i][j][0],f[i-1][j-k][0]+1);
						f[i][j][1]=max(f[i][j][1],f[i-1][j-k][1]-1);
					}
				} else {
					if(k&1){
						f[i][j][0]=max(f[i][j][0],f[i-1][j-k][0]+1);
						f[i][j][1]=max(f[i][j][1],f[i-1][j-k][1]-1);
					} else {
						f[i][j][0]=max(f[i][j][0],f[i-1][j-k][1]);
						f[i][j][1]=max(f[i][j][1],f[i-1][j-k][0]);
					}
				}
			}
		}
	}
	printf("%d\n",max(f[len][n][0],f[len][n][1]));
	return 0;
}