1. 程式人生 > >魏傳之長阪逆襲、蜀傳之單刀赴會、吳傳之火燒連營

魏傳之長阪逆襲、蜀傳之單刀赴會、吳傳之火燒連營

A、魏傳之長阪逆襲

時間限制: 1 Sec  記憶體限制: 128 MB

題目描述

眾所周知,劉備在長阪坡上與他的一眾將領各種開掛,硬生生從曹操手中逃了出去,隨後與孫權一起火燒赤壁、佔有荊益、成就霸業。而曹操則在赤壁一敗後再起不能,終生無力南下。

建安二十五年(220年),曹操已到風燭殘年,但仍難忘當年長阪的失誤,霸業的破滅。他想如果在劉備逃亡的路中事先佈下一些陷阱,便能拖延劉備的逃脫時間了。你作為曹操身邊的太傅,有幸穿越到了208年的長阪坡,為大魏帝國貢獻一份力,佈置一些陷阱。但時空守衛者告訴你你不能改變歷史,不能拖增大劉備的最大逃脫時間,但你身為魏武之仕,忠心報國,希望能新增一些陷阱使得劉備不論怎麼逃跑所用的時間都一樣。

已知共有n個據點,n-1條棧道,保證據點聯通。1號據點為劉備軍逃跑的起點,當劉備軍跑到某個據點後不能再前進時視為劉備軍逃跑結束。在任意一個棧道上放置1個陷阱會使通過它的時間+1,且你可以在任意一個棧道上放置任意數量的陷阱。

現在問你在不改變劉備軍當前最大逃跑時間的前提下,需要新增最少陷阱,使得劉備軍的所有逃脫時間都儘量的大。

輸入

 第一行一個數n,表示據點個數。

    接下來n-1行每行三個數,ai、bi、ti,表示從據點ai通過第i個棧道到bi耗時ti

輸出

僅包含一個數,為需要新增的最少陷阱數。

樣例輸入

3 1 2 1 1 3 3

樣例輸出

2

提示

【資料規模和約定】

對於 5%的資料,1<=n<=100000,1<=ti<=200000

對於 100%的資料,1<=n<=500000,0<ti<=1000000

這是一棵樹

每次對於深度越低的點能放多少陷阱就放多少陷阱

#include<cstdio> 
#include<iostream>
#define ll long long
using namespace std;
int read()
{
    int ret=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret;
}
 
const int N=1e6+5; 
int n,num,id[N];
ll mx,ss[N],a[N],ans;
int cnt,he[N],to[N],nxt[N],w[N];
 
inline void add(int u,int v,int k)
{
    to[++cnt]=v,w[cnt]=k,nxt[cnt]=he[u],he[u]=cnt;
}
 
void dfs(int u,ll s)
{
    bool ch=0;
    for(int e=he[u];e;e=nxt[e])
        ch=1,dfs(to[e],s+w[e]);
    if(!ch) mx=max(mx,s),id[++num]=u,ss[num]=s;
}
 
void dfs1(int u)
{
    if(he[u]) a[u]=1e18;
    for(int e=he[u];e;e=nxt[e])
    {
        int v=to[e];
        dfs1(v); a[u]=min(a[u],a[v]);
    }
    for(int e=he[u];e;e=nxt[e])
        ans+=a[to[e]]-a[u];
}
int main()
{
    n=read();
    for(int i=1;i<n;i++)
    {
        int u=read(),v=read(),k=read();
        add(u,v,k);
    }
    dfs(1,0);
    for(int i=1;i<=num;i++)a[id[i]]=mx-ss[i];
    dfs1(1);
    printf("%lld",ans);
    return 0;
}

B、蜀傳之單刀赴會

時間限制: 1 Sec  記憶體限制: 128 MB

題目描述

【題目背景】

公元215年,劉備取益州,孫權令諸葛瑾找劉備索要荊州。劉備不答應,孫權極為惱恨,便派呂蒙率軍取長沙、零陵、桂陽三郡。長沙、桂陽蜀將當即投降。劉備得知後,親自從成都趕到公安(今湖北公安),派大將關羽爭奪三郡。孫權也隨即進駐陸口,派魯肅屯兵益陽,抵擋關羽。雙方劍拔弩張,孫劉聯盟面臨破裂,在這緊要關頭,魯肅為了維護孫劉聯盟,不給曹操可乘之機,決定當面和關羽商談。“肅邀羽相見,各駐兵馬百步上,但諸將軍單刀俱會”。雙方經過會談,緩和了緊張局勢。隨後,孫權與劉備商定平分荊州,“割湘水為界,於是罷軍”,孫劉聯盟因此能繼續維持。

【問題描述】

關羽受魯肅邀請,為了大局,他決定冒險赴會。他帶著侍從周倉,義子關平,騎著赤兔馬,手持青龍偃月刀,從軍營出發了,這就是歷史上赫赫有名的“單刀赴會”。關羽平時因為軍務繁重,決定在這次出行中拜訪幾個多日不見的好朋友。然而局勢緊張,這次出行要在限定時間內完成,關公希望你能夠幫助他安排一下行程,安排一種出行方式,使得從軍營出發,到達魯肅處赴會再回來,同時拜訪到儘可能多的朋友,在滿足這些條件下行程最短。注意拜訪朋友可以在赴會之前,也可以在赴會之後。現在給出地圖,請你完成接下來的任務

輸入

   第一行n,m,k,t,代表有n個地點,m條道路,有k個朋友(不包括魯肅),以及限定時間t(行走1單位長度的路程用時1單位時間)。

    接下來m行,每行有x,y,w三個整數,代表x和y之間有長度為w的道路相連。

    接下來一行有k個整數,代表朋友所在的都城編號(保證兩兩不同,且不在1和n)

   (我們約定1是關羽的營地,n是魯肅的營地)

輸出

輸出兩個整數,分別是最多可以拜訪的朋友數,以及在這種情況下最少需要耗費的時間,如果連到達魯肅再回來都無法完成,輸出一個-1就可以了。

樣例輸入

5 7 2 15 1 2 5 1 3 3 2 3 1 2 4 1 3 4 4 2 5 2 4 5 3 2 4

樣例輸出

2 14

提示

【資料規模和約定】

有10%資料,n<=10,m<=50,k<=5;

有10%資料,k=0;

有10%資料,k=1;

另30%資料,k<=5;

對於100%資料,n<=10000,m<=50000,k<=15,t<=2147483647,w<=10000

先跑一遍dijkstra,然後狀壓dp

#include<cstdio>
#include<queue>
#include<iostream>
#define ll long long
using namespace std;
int read()
{
    int ret=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') 
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret;
}
  
const int N=2e5+5;
int n,m,p,t,a[25],d[N],dis[25][25],num[N];
ll f[25][N];
int cnt,he[N],to[N],nxt[N],w[N],ans,anss;
bool fl[N];
struct NA{
    int id,x;
};
bool operator >(NA i,NA j)
{
    return i.x>j.x;
}
priority_queue<NA,vector<NA>,greater<NA> >q;
   
inline void add(int u,int v,int k)
{
    to[++cnt]=v,nxt[cnt]=he[u],w[cnt]=k,he[u]=cnt;
}
  
int dist(int s,int g)
{
    while(!q.empty()) q.pop();
    for(int i=1;i<=n;i++) fl[i]=0,d[i]=2e9;
    q.push((NA){s,d[s]=0});
    for(int i=1;i<=n;i++)
    {
        while(!q.empty()&&fl[q.top().id]) q.pop();
        if(q.empty()) break;
        int u=q.top().id; q.pop(); fl[u]=1;
        if(u==g) break;
        for(int e=he[u];e;e=nxt[e])
        {
            int v=to[e];
            if(!fl[v]&&d[v]>d[u]+w[e]) 
                q.push((NA){v,d[v]=d[u]+w[e]});
        }
    }
    return d[g];
}
  
int main()
{
    n=read(),m=read(),p=read(),t=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read(),k=read();
        add(u,v,k),add(v,u,k);
    }
    for(int i=1;i<=p;i++) a[i]=read();
    a[p+1]=1,a[p+2]=n;
    for(int i=1;i<=p+2;i++)
        for(int S=0;S<1<<p+2;S++) f[i][S]=1e15;
    ll x=dist(1,n);
    if(x==2e9||x<<1>t) 
    {
        puts("-1"); return 0;
    }
    for(int i=1;i<p+2;i++)
        for(int j=i+1;j<=p+2;j++) 
            dis[j][i]=dis[i][j]=dist(a[i],a[j]);
    f[p+1][1<<p]=0;
    for(int S=0;S<1<<p+2;S++)
        if(!(S&1<<p))
        {
            int SS=S|1<<p;
            for(int i=1;i<=p+2;i++)
                for(int j=1;j<=p+2;j++)
                    if(i!=j&&1<<j-1&SS&&f[j][SS]!=1e15&&dis[i][j]!=2e9)
                        f[i][SS|1<<i-1]=min(f[i][SS|1<<i-1],f[j][SS]+dis[i][j]);
        }
    for(int S=1;S<1<<p+2;S++) num[S]=num[S-(S&-S)]+1;
    ans=0; anss=x<<1;
    for(int SS=0;SS<1<<p;SS++)
    {
        int S=SS|1<<p|1<<p+1;
        for(int i=1;i<=p+2;i++)
        {
            if((1<<i-1)&S&&f[i][S]!=1e15)
            {
                ll ti=f[i][S]+dis[i][p+1];
                if(ti<=t)    
                    if(ans==num[S]) anss=min(anss,(int)ti);
                        else if(ans<num[S]) ans=num[S],anss=ti;
            }
        }
    }
    printf("%d %d\n",ans-2,anss);
    return 0;
}

C、吳傳之火燒連營

時間限制: 1 Sec  記憶體限制: 128 MB  Special Judge

題目描述

【題目背景】

蜀漢章武元年(221年),劉備為報吳奪荊州、關羽被殺之仇,率大軍攻吳。吳將陸遜為避其鋒,堅守不戰,雙方成對峙之勢。蜀軍遠征,補給困難,又不能速戰速決,加上入夏以後天氣炎熱,以致銳氣漸失,士氣低落。劉備為舒緩軍士酷熱之苦,命蜀軍在山林中安營紮寨以避暑熱。陸遜看準時機,命士兵每人帶一把茅草,到達蜀軍營壘時邊放火邊猛攻。蜀軍營寨的木柵和周圍的林木為易燃之物,火勢迅速在各營漫延。蜀軍大亂,被吳軍連破四十餘營。陸遜火燒連營的成功,決定了夷陵之戰蜀敗吳勝的結果。

【問題描述】

劉備帶兵深入吳境,陸遜卻避而不出,蜀軍只得在山林中安營紮寨。而劉備在紮營時卻犯了兵家大忌,將兵營排列成一條直線,遠遠看去,就像是一條串著珠子的鏈,美其名曰:鏈寨。如果吳軍將領是一般人,那麼這也許不算什麼,而陸遜何許人也,他可是江東才子,能力不低於周瑜的一代儒將。他看到劉備這樣排陣,心生一計,決定用火攻破陣。然而,火計除了要有風,選定引火點也非常重要,對於劉備的佈陣,最佳引火點一定是n個兵營中的一個。而因為風水輪流轉,每天的最佳引火點都不一樣。我們給每個兵營定下一個固定不變的火攻值Ai,每天定下一個風水值K,對於每天的最佳引火點,顯然是所有兵營中火攻值與風水值異或的結果最大的那一個兵營。然而,陸遜是個謹慎的人,他要觀察時機,在m天中選定一個最佳的進攻的日期,為此他演算出了這m天每天的風水值,然後他希望你能夠告訴他這m天每天最佳引火點的兵營編號。

輸入

第一行n,m,代表有n個兵營,m天。

    接下來一行有n個非負整數,代表這n個兵營的火攻值。

    接下來一行有m個非負整數,代表這m天的風水值。

輸出

輸出共m行,每行輸出一個整數,代表第m天最佳引火點的編號。

如果存在多個最佳引火點使得火攻值與風水值的異或值最小,請任意輸出一組解即可。

樣例輸入

3 2 1 2 3 4 5

樣例輸出

3 2

提示

【樣例解釋】

對於第1天,由於4 xor 1=5, 4 xor 2=6, 4 xor 3=7,選擇第3個引火點是最佳的。

對於第2天,由於5 xor 1=4, 5 xor 2=7, 5 xor 3=6,選擇第2個引火點是最佳的。

【資料規模和約定】

對於30%資料,n<=1000,m<=1000

對於100%資料,n<=100000,m<=100000, 0<=k,ai<=2147483647

這不是一道常規的trie樹?

#include<cstdio> 
using namespace std;
int read()
{
    int ret=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret;
}
 
const int N=1e7+5;
int n,m,p,now,nxt[N][2],ans[N];
 
int main()
{
    n=read(),m=read();
    p=1;
    for(int i=1;i<=n;i++)
    {
        int x=read();
        now=1;
        for(int j=31;j;j--)
        {
            int t=(x>>(j-1))&1;
            if(nxt[now][t]) now=nxt[now][t];
                else now=nxt[now][t]=++p,ans[p]=i;
        }
    }
    for(int i=1;i<=m;i++)
    {
        int x=read();
        now=1;
        for(int j=31;j;j--)
        {
            int t=!((x>>(j-1))&1);
            if(!nxt[now][t]) 
            {
                now=nxt[now][!t];
            } else now=nxt[now][t];
        }
        printf("%d\n",ans[now]);
    }
    return 0;
}