1. 程式人生 > >CodeForces CF #500 Div.2 (D,E已更新)

CodeForces CF #500 Div.2 (D,E已更新)

這次的題目真玄學啊。。。半個小時A掉了ABC題,然後就坐著喝了兩個小時的茶

A. Piles With Stones

題意,給你n個數字,ai表示在i-th的位置上有ai塊石頭。已知現在有兩種操作,分別是把某個石頭移到另一個位置,或者拿走一塊。

然後。。。再給你n個數字,問你是否在足量次操作後可以變成後面的狀態。

我寫程式碼的時間還沒讀題的時間長。。。。就是上面求和,下面求和,然後比大小。。。。畢竟石頭不可能變多,所以上面一定大於等於下面。滿足這個條件就可以了。

B. And

這題也是水題。給你n個數字和一個m。問是否用m去和這些數字按位&之後能出現兩個相同的數字(也可能不需要任何 操作),問最少需要幾次操作。不存在則輸出-1。

顯然,有以下四種情況:
1.開始就有相同的,那麼答案是0;
2.在不滿足的1情況下,如果存在某個數x,和另一個數y,滿足x&m==y,那麼答案是1
3.在不滿足的1,2情況下,如果存在某個數x,和另一個數y,滿足x&m==y&m,那麼答案是2
4.否則答案是-1

那麼我們儲存兩個hash表,對一個數x分別儲存x(hash1)和x&m(hash2)是否已經存在過。

然後當我們讀取一個新的y的時候,檢視hash1[y]和hash2[y]是否存在。再對y&m進行一樣的操作。然後按照上面的分類儲存最小的結果。最後更新hash表。

最後應該是O(n)的複雜度。

#define INI(x) memset(x,0,sizeof(x))
#define MIN(x,y) (((x)<(y))?(x):(y))
int ans=3;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='0')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

bool a[100005],b[100005]; 
int n,m;

int main()
{
    INI(a);
    INI(b);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int temp=read();
        int ttemp=temp&m;
        if(a[temp])ans=0;
        if(b[temp])ans=MIN(ans,1);
        else
        {
            if(a[ttemp])ans=MIN(ans,1);
            if(b[ttemp])ans=MIN(ans,2);
        }
        a[temp]=1;
        b[ttemp]=1;
    }
    if(ans==3)cout<< "-1"<< endl;
    else cout<< ans<< endl;
}

感激涕零,終於會打程式碼塊了

C. Photo of The Sky

這道題我wa了一次,然後改了一下演算法就過了,算道思維題吧。

題意是說,給你2*n個數,組成n個座標,問可以把這n個點全部筐進去的最小的橫平豎直的矩形的面積。

簡單來說,就是把2*n個數等分成兩組,使兩組的最大值與最小值的差的乘積最小。

然後我馬上就能想到,那顯然最大的數和最小的數不能在一組,那麼排序後從中間切開不就好了嗎!!

然後我自信滿滿的手寫了一個樣例:

1 2 3 4 5 6

從中間分開

1 2 3 | 4 5 6

假設1和6不動,這個時候我們把左邊的任何一個數和右邊的某個數換位置,那麼顯然答案會變大,這個不難證明,這裡就略過了。

然後我就提交了,然後我就WA了。

然後我發現了另一組測試資料是這樣的:

1 2 2 3

如果把2 2分到一組的話,那麼面積是0。

但是用我的演算法就是1。

然後我才意識到我的前提有問題:最大值和最小值也可能在同一組。

那麼我們考慮,如果最大值和最小值分列兩組,那麼就用上面的演算法,如果最大值和最小值在同一組,那麼這一組的差值已經確定了,我們希望另一組的差值最小。這樣的話,另一組的n個數必定是連在一起的,否則範圍必然偏大。那麼我們窮舉另一組裡每組連續的n個數的左右端點的差值,記錄最小值。

最後把兩種情況的結果比較,得到最終結果。

#define INI(x) memset(x,0,sizeof(x))
#define MIN(x,y) (((x)<(y))?(x):(y))
int n;
long long a[200005];
long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='0')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}


int main()
{
    n=read();
    for(int i=0;i<2*n;i++)a[i]=read();
    sort(a,a+n*2);
    long long ans=(a[n-1]-a[0])*(a[2*n-1]-a[n]);
    for(int i=1;i<n;i++)ans=MIN(ans,(a[2*n-1]-a[0])*(a[i+n-1]-a[i]));
    cout<<ans<<endl;
}

最後結果是排序的NlogN以及N的窮舉,所以是O(NlogN)的複雜度。

DEF:不會

其實還有有一點發現的。。。就是D題應該是找一個構造法來解,然後過程中發現關鍵點已經給出,那麼就更新結果,最後得到最優答案。。。但是我找不到構造法。

E題是DP題,我敢拍著胸脯(輪椅)講,絕對是DP!

補充2018.8.2:

D.Chemical table

我無論如何也想不到,這居然是一道dfs題。。。

給你一個n*m的矩形,一開始有q個格子上被標記。對於任意兩行兩列,如果交匯的四個格子中有三個被標記,那麼第4個會被自動標記。問你至少需要手動標記幾個格子,使得整個矩形內的格子都被標記。

這道題做的時候我也沒有好的思路,那麼我先講解法,然後補充解題思路吧(俗稱馬後炮)。

我們構造一張圖,總共有n+m個點,分別對應矩形的兩條邊。

先證一個引理,我們假設一個矩形四個頂點分別為:

(a,c)(a,d)
(b,c)(b,d)

(其中a,b是1~n的邊上選的兩點,c,d是1~m的邊上選的兩點,這裡先不考慮他們的數值,就算a==b或a==c我們這裡也視為不同的點,後面會解釋)

假設我們已經有了(a,c)(a,d)(b,c)三個點,那麼我們在構造的圖上把它們相連,會得到:

d–a–c–b

這樣相連的四個點,根據定義,此時d–b也可以被連上了。

再反過來驗證,隨意找一個a–b–c–d,都可以構造成矩形的形式,然後將a–d連上。

對於更長的模型a–b–c–d–e–f我們經過裁剪可以保證a分別與b,d,f相連。對餘下的點也同理,對更長的點也同理。也就是說,對一長串的點而言,我們對他們編號為1–2–3–4–5–6–…–x,我們的偶數項可以通過構造與其他所有奇數項相連,vice cerse.

接下來我們再看題目,我們把這n+m個點分為1~n和n+1~n+m兩組,希望每一點都與非本組的每個點相連。顯然我們的初始條件中不會出現同組之間互相連線的情況,必定是一串兩組中的點交替出現的圖,所以是滿足題意的。那我們運用上一題的結論,保證只要點在同一個聯通塊中,那麼可以在不新增新點的情況下滿足聯通塊中每一點都與聯通塊中非本組的每個點相連。

那麼我們只需要統計有多少聯通塊,然後數量-1就是最終答案。所以一通dfs即可。因為資料範圍比較大,但是邊小於等於200000條,鄰接矩陣必死無疑,所以這裡我們用一個前向星來儲存。(其實也可以用vector,但是慢啊,而且醜陋。)

解題思路:我把這種題目定義為“有限構造”類,就是在一個有限的域中,存在某種遞推關係,可以嘗試用構造成圖的方法來解答。有限是因為不能去解決像比方說求斐波那契數列那樣的問題(但是如果有一個取模,問至少需要幾組初始資料才能填滿它的完系,這樣有限規模的題目是可以構造圖來解決的)。存在的遞推關係則是用於構造圖中的邊。

值得注意的是,這只是一個初步的想法,既不必要,也不一定總是可行。但是我認為這種思路對於表現遞推關係很有幫助,就這道題而言,用很簡潔的方式刻畫了矩形補角的關係。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define INI(x) memset(x,0,sizeof(x))

void read(int &x)
{
    x=0;int f=-1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return;
 }//快讀

 struct graph{int to,next;}edge[400005];
 int tail[400005];
 int edge_cnt=0;
 void add_edge(int f,int t)
 {
    edge[++edge_cnt].to=t;
    edge[edge_cnt].next=tail[f];
    tail[f]=edge_cnt;   
    return;
 }//前向星

 int n,m,q,r,c;
 bool vis[400005]; 
 int ans=-1;//答案就是聯通塊數目-1 

 void INITIATE()
 {
    cin>>n>>m>>q;
    INI(vis);
    INI(tail);
    for(int i=0;i<q;i++)
    {
        read(r);read(c);
        c=c+n;
        add_edge(r,c);
        add_edge(c,r);
    }
    return;
 }//初始化模組 

 void dfs(int x)
 {
    vis[x]=1;
    for(int i=tail[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!vis[to])dfs(to);
    }
    return;
 }

 void SOLVE()
 {
    for(int i=1;i<=m+n;i++)
    {
        if(!vis[i])
        {
            ans++;
            dfs(i);
         }
     }
     return;
 }

 int main()
 {
    INITIATE();
    SOLVE();
    cout<<ans<<endl;
 }

E.Hills

這題果然是DP!!!我轉移方程寫對了四分之三。。。一個特殊情況沒有考慮到位,稍加思索還是A掉了,其實也算是水題吧(並不)。

題面如下:輸入一串數字代表一串山坡的高度,選一些山頭造房子,可以建房子的山坡的定義是這個山坡的高度嚴格比旁邊兩個山坡大。我可以用挖掘機挖山坡使其高度-1(可以減到負數)。現在要分別找到想建1-n/2個房子,問每種情況下要挖掉的總高度的最小值。

這樣的題目不dp一下也說不過去啊,具有非常明顯的最優子結構。接下來就是表示式的選擇。這裡主要有兩個要素:用i,j表示到i位置為止總共造了j個房子。但是因為相鄰格子之間有些亂七八糟的東西,我們希望用多一個維度來表示i的位置上有沒有造。

最終得到的是表示式如下:dp[i][j][c] (c=1或0)

其中i表示到第i位置為止,j表示已經造了j個,c==1時表示j位置上有房子,c==0時沒有。

接下來需要解決遞推式和初始條件,我們先看遞推式:

1.對於c==0的情況,因為反正這個位置不造房子,那麼上一個格子造不造都無所謂,並且對下一格也沒有影響,所以就是上一格造或不造的狀態中較小的一個:

dp[i][j][0] = MIN (dp[i-1][j][0],dp[i-1][j][1])

2.對於c==1的情況。上一個山頭如果已經造了房子,那麼這個山頭必定不可能造房子(因為上一個山頭造房子的時候已經比這個山頭高了,造完了又不能推低,所以這個山頭總是比上一個山頭矮),所以從i-2的位置開始推。上一個山頭如果還沒造房子,那這裡確實可以造,但是我希望知道上上的山頭造了沒有(如果造了,那麼可能會影響上一個山頭的高度),所以也應該從i-2開始推,我這裡就是沒考慮這一點,所以就出了偏差。

我們定義cost1[i]為i山頭山頭i-1需要被削去的高度,cost2[i]對應地表示山頭i+1需要被削去的高度(假設上個山頭沒被砍過),可以預處理。另外定義一個tempcost1表示山頭i-1被砍過的狀態下需要再砍多少。以下給出對應的公式:

cost1[i]=MAX(h[i-1]-h[i]+1,0)
cost2[i]=MAX(h[i+1]-h[i]+1,0)
tempcost1=MAX(0,1-h[i]+MIN(h[i-1],h[i-2]-1))

其中h[i]為山頭高度,h[0]=h[n+1]=0(因為最低的山為1)。

以下是轉移方程:

dp[i][j][1]=MIN(dp[i-2][j-1][1]+cost2[i]+tempcost1,dp[i-2][j-1][0]+cost1[i]+cost2[i]);

考慮完遞推式,就該考慮邊界條件了,這邊目前而言我還沒發現比較好的定邊界條件的辦法,一般都是憑感覺。希望有心得的讀者可以分享一下。我這邊是把dp[i][i][1]也就是到i位置放下第一個房子的花費作為邊界條件,手算一下應該沒什麼問題就過了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MIN(x,y) (((x)<(y))?(x):(y))
#define MAX(x,y) (((x)>(y))?(x):(y))
#define ABS(x) (((x)>0)?(x):(-x))

const int maxn=5005;
int dp[maxn][maxn][2];
int a[maxn];
int cost1[maxn];
int cost2[maxn];
int n,k;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='0')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int main()
{
    cin>>n;
    k=(n+1)/2;

    for(int i=0;i<=n;i++)
    for(int j=0;j<=k;j++)
    dp[i][j][0]=dp[i][j][1]=1e9+7;
    for(int i=0;i<=n;i++)dp[i][0][0]=0;

    a[0]=a[n+1]=0;
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++)
    {
        cost1[i]=MAX(a[i-1]-a[i]+1,0);
        cost2[i]=MAX(a[i+1]-a[i]+1,0);
    }
    for(int i=1;i<=n;i++)dp[i][1][1]=cost1[i]+cost2[i];

    for(int j=1;j<=k;j++)
    for(int i=1;i<=n;i++)
    {
        dp[i][j][0]=MIN(dp[i-1][j][1],dp[i-1][j][0]);
        int tempcost1=MAX(0,1-a[i]+MIN(a[i-1],a[i-2]-1));
        if(i!=1)dp[i][j][1]=MIN(dp[i-2][j-1][1]+cost2[i]+tempcost1,dp[i-2][j-1][0]+cost1[i]+cost2[i]);
    }

    for(int i=1;i<=k;i++)cout<<MIN(dp[n][i][0],dp[n][i][1])<<" ";
}