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;
}