1. 程式人生 > >CodeForces CF #503 Div.2

CodeForces CF #503 Div.2

今天繼續講述我的掉分之路=。=

A. New Building for SIS

給你一個n棟樓,每棟樓高度為h。在對任意x滿足a<=x<=b,有在x樓有樓梯,連線相鄰的兩個樓。然後是k組詢問,回答兩個座標之間的最短路徑的長度。

樣例

設兩個座標為(x1,y1),(x2,y2)。這題有點類似於求曼哈頓距離,所以我們把橫座標和縱座標分開來看。

那麼對於橫座標而言,距離必定是fabs(x1-x2)。而對於縱座標而言,如果y1>b&&yw>b,那麼必須要繞到b層才可以通行,代價是(y1-b+y2-b),對y1

int main()
{
    long
long n,h,a,b,k; cin>>n>>h>>a>>b>>k; for(long long i=0;i<k;i++) { long long x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; long long ans=fabs(x2-x1); if(x1==x2)ans=fabs(y1-y2); else if(y1>b&&y2>b)ans+=y1-b+y2-b; else
if(y1<a&&y2<a)ans+=a-y1+a-y2; else ans+=fabs(y2-y1); cout<<ans<<endl; } return 0; }

B. Badge

給你一張有向圖,每個點只要一個出度,問從某個點出發,第一次訪問到一個被訪問過的點是什麼。我一開始打了個tarjan來找環,然後發現直接模擬就好了。。。

比上一題簡單…我覺得這次題目出的實在太懊糟了。

int main()
{
    bool vis[1005];
    int nx[1005];
    int
n; cin>>n; for(int i=1;i<=n;i++)cin>>nx[i]; for(int i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); int cur=i; while(vis[cur]==0) { vis[cur]=1; cur=nx[cur]; } cout<<cur<<" "; } }

C. Political Metaphor

啊不好意思打錯了,是C. Elections

第三題是說,有有n個政黨,m個選民。每個選民用一定的BTC就可以收買。問你最少畫多少錢能保證當選。

這個題目啊,exciting。我們會發現這題正面是很難做的,因為當你買的時候,改變的不止是自己的選票,還有沒買走的那個政黨的選票,這樣一個動態的環境你比較難處理。但是我們又很想用貪心來做這個題,所以我們至少要讓其中的某些變數定下來,那我們換一下思路,從反面來處理。

我們是政黨1,我們窮舉政黨1可能的最終選票數。如果它小於政黨一開始擁有的選票數,那麼顯然是不可能的,所以我們可以從一開始擁有的選票數開始(這是不搞黑幕的情況),窮舉到擁有最多選票的政黨的選票數+1(這是不動這個政黨的選民,去拉其他政黨的選民的票的情況)。

不妨設我們現在窮舉到i,那麼顯然擁有最多選票的政黨至多為i-1,那麼我們在i-1的位置上切一刀,把高於i-1位置的選民都買走,設一開始擁有的選票數是pre,這裡買了det個選民,那麼接下來我們記錄還需要買need個選民才能達到i個選民:

need=i-pre-det(1)

如果need<0那麼說明買來這些已經超過了我要求的i,那麼等於是不可能使自己的選民數恰好為i還能滿足題意的,那麼捨去。

如果need>=0那麼我們還需要購買need個選民,我們在剩下的選民中任意選取(因為現在買誰的人都是一樣的了,所以就可以貪心啦)need個費用最低的選民,記錄為ans。再加上上面切那一刀花掉的錢,更新我們的最小答案minans。

當然閉著眼睛不優化的話複雜度是O(N3)那會T是顯而易見的了。但是我做的時候沒有想到了,T了就很難受,又做不出來,怎麼辦呢?我心態炸了(因為A題我wa了四次),就去搓爐石,突然看到一個4/4亡語召喚兩個2/2亡語召喚兩個1/1,我馬上想到這不是logN嗎,繼而想到優先佇列,繼而優化了一下,繼而ac了。

所以最終結果是O(N2logN)

#define INI(x) memset((x),0,sizeof(x))
#define MIN(x,y) (((x)<(y))?(x):(y))

struct voter{
    int b,p;
    voter(long long x,long long y):b(x),p(y){}
};
bool operator < (voter a,voter b){return a.b>b.b;};

long long n,m;
vector<long long> cost[3005];
vector<long long> sum[3005];
long long ansr=0xfffffffffffffff,ans=0;
bool check(int h)
{
    ans=0;
    int pick[3005];
    for(int i=2;i<=m;i++)pick[i]=0;
    int pre=cost[1].size();
    int det=0;
    for(int i=2;i<=m;i++)
    {
        int dt=cost[i].size()-h+1; 
        if(dt>0&&cost[i].size())det+=dt,ans+=sum[i][dt-1],pick[i]=dt;
    }
    if(det>h-pre)return 0;
    int need=h-pre-det;
    priority_queue<voter> nx;
    for(int i=2;i<=m;i++)if(pick[i]<cost[i].size())
    {
        voter ttt(cost[i][pick[i]],i);
        nx.push(ttt);
        pick[i]++;
    }
    while(need--)
    {
        if(nx.empty())
        {
            ans=0xfffffffffffffff;
            break;
        }
        voter topp=nx.top();
        nx.pop();
        ans+=topp.b;
        int ind=topp.p;
        if(pick[ind]<cost[ind].size())
        {
            voter ttt(cost[ind][pick[ind]],ind);
            nx.push(ttt);
            pick[ind]++;
        }
    }
    return 1;
}


int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int pp,cc;
        cin>>pp>>cc;
        cost[pp].push_back(cc);
    }
    for(int i=1;i<=m;i++)sort(cost[i].begin(),cost[i].end());
    int maxlen=-1; 
    for(int i=1;i<=m;i++)
    {
        if(!cost[i].empty())sum[i].push_back(cost[i][0]);
        for(int j=1;j<cost[i].size();j++)sum[i].push_back(sum[i][j-1]+cost[i][j]);
        int mm=cost[i].size();
        if(mm>maxlen)maxlen=mm;
    }
    int l=0;
    if(!cost[1].empty())l=cost[1].size();
    int r=maxlen+1;
    for(int i=l;i<=r;++i)
    {
        if(check(i))ansr=MIN(ansr,ans);
    }
    cout<<ansr<<endl;
    return 0;
}

D.The hat

互動題,不會

E.Sergey’s problem

http://codeforces.com/contest/1019/problem/C

直覺告訴我是dp做完來補

嗯對,我早就說了,這是一個O(n+m)的構造題。

我寫一下虛擬碼,寫完就懂了。

首先在外面儲存一個pick[i]陣列,表示選擇點i沒有。然後儲存一個vis[i]陣列,儲存i被訪問了幾次。最後儲存一個pnt,表示已經選到了第pnt的元素。pnt初始值為1。ans儲存目前已經選取了幾個點。

  • 虛擬碼

dfs()
①從當前的pnt開始找到下一個沒有被訪問過的元素
②如果pnt已經大於n,那麼return
③pick[pnt]=true;vis[pnt]++;
④將每一個pnt點指向的點的訪問數+1
⑤將pnt儲存進一個臨時變數save,因為進入下一層dfs以後pnt就會變化了
⑥dfs();
⑦若此時save點(也就是之前的pnt點)被訪問了超過一次,那麼pick[save]=false,vis[save]–,且將每一個save點指向的點訪問數-1
⑧return;

  • 真程式碼
void dfs()
{
    while(pnt<=n&&vis[pnt])pnt++;//找到第一個未選取,未訪問的點pnt
    if(pnt>n)return;//全部選取或訪問過了,返回
    vis[pnt]++;//訪問次數+1
    pick[pnt]=1;ans++;//pnt被選取
    for(int i=tail[pnt];i;i=e[i].next)vis[e[i].to]++;//可達點訪問次數+1
    int point_save=pnt;//儲存這個點的位置,防止在dfs中丟失 
    dfs();//去尋找下一個點
    if(vis[point_save]!=1)//若還被其他點訪問過了,那麼這個點就不取
    {
        pick[point_save]=0;ans--;
        for(int i=tail[point_save];i;i=e[i].next)vis[e[i].to]--; 
     } 
    return;
}
  • 證明

(這個證明相當玄學,我自己都覺得不靠譜)

注:【】中的內容表示在【】情況會發生

首先對於入度為0的點,一定是會被選中的。因為它不會被其他點訪問到,所以vis始終為0,直到被選中。

其次,對於入度不為0的任何一個點,假設演算法沒有步驟⑦,即沒有彈出的步驟,那麼只有3種可能:
(1)有一個被選中的點指向它
(2)本身被選中且不被其他被選中的點指向
(3)本身被選中且有一個被選中的點指向它

當符合情況(1)時,那麼這個點必定是合法的,並且有一個被選中的點指向它
當符合情況(2)時,這個點也必定是合法的(如果它指向一個被選中的點,那麼這個點將在下面的步驟中被刪除,所以我們將其視作合法的點)
當符合情況(3)時,那麼這個點不合法

那麼此時,我們需要將被這個點訪問的且被選中的點踢出去,來保持我們v的合法性。

我們考慮一個被選中的點v,然後取出它指向的一個被選中的點u,然後將u踢出去。

將u踢出去以後,那麼對於任何一個u指向的點w,如果w之前是情況(1),那麼將變為情況(1)【當有另一個被選中的點指向w時】或情況:
(4)有一個被選中的點兩步可以抵達它【沒有另一個被選中的點指向w時】

如果w之前是情況(2),那麼將變為情況(2)【當有另一個被選中的點指向w時】或情況(1)【沒有另一個被選中的點指向w時】

如果w之前是情況(3),那麼將變為情況(3)【當有另一個被選中的點指向w時】或情況(2)【沒有另一個被選中的點指向w時】

我們可以看到,在進行完操作之後,所有被操作波及的點的最終狀態都是(1)(2)或(4),滿足題意。我們這樣可以說明,在去除所有的狀態(3)的點之後,餘下的每個點都將處於合法的狀態上,所以演算法的正確性得證。

  • 思路

講一下思路(其實我是沒什麼思路的)。

首先我們看到一個1e6的n,一個1e6的m,馬上想到複雜度最大不能超過NlogN。然而這個題看著就像是O(n)的題目,所以不難想到dp,所以不難想到是一個構造演算法,關鍵是這個怎麼構造出來的。作為一個偷窺了dalao的標程以後才做出來的蒟蒻我實在不好意思吹噓我是怎麼想到這麼構造的,但是我覺得這種兩面都要顧及的題目,可以考慮先解決一面,另一面考玄學的數學證明來解決(霧)。

  • 複雜度

最後研究一下複雜度。
首先,pnt從1~n走一遍,不會回溯,所以複雜度是O(n)
其次,對於每條邊,最多被訪問兩次,所以複雜度是O(m)
綜上,複雜度是O(m+n),可以線上性時間內解決。

#define INI(x) memset(x,0,sizeof(x))
const int MAXN=1e6+3;
int n,m,pnt=1,ans=0;

bool pick[MAXN];
int vis[MAXN];

struct E{int to,next;}e[MAXN];
int tail[MAXN];
int cnt=0;
void edge_add(int f,int t){e[++cnt].to=t;e[cnt].next=tail[f];tail[f]=cnt;}

void read(int &x)
{
    x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return;
}

void INITIATE()
{
    INI(pick);
    INI(vis);
    INI(tail);
    read(n);
    read(m);
    int f,t;
    for(int i=0;i<m;i++)read(f),read(t),edge_add(f,t);
    return;
}

void dfs()
{
    while(pnt<=n&&vis[pnt])pnt++;//找到第一個未選取,未訪問的點pnt
    if(pnt>n)return;//全部選取或訪問過了,返回
    vis[pnt]++;//訪問次數+1
    pick[pnt]=1;ans++;//pnt被選取
    for(int i=tail[pnt];i;i=e[i].next)vis[e[i].to]++;//可達點訪問次數+1
    int point_save=pnt;//儲存這個點的位置,防止在dfs中丟失 
    dfs();//去尋找下一個點
    if(vis[point_save]!=1)//若還被其他點訪問過了,那麼這個點就不取
    {
        pick[point_save]=0;ans--;
        for(int i=tail[point_save];i;i=e[i].next)vis[e[i].to]--; 
     } 
    return;
}

int main()
{
    INITIATE();
    dfs();
    cout<<ans<<endl;
    for(int i=1;i<=n;i++)if(pick[i])cout<<i<<" "; 
 }