1. 程式人生 > >Codeforces好題專欄(前三道)

Codeforces好題專欄(前三道)

為了上分,博主打算開一篇部落格來記錄下前三道遇到的好題.
C. String Reconstruction
題意:給你若干個子串首字母所在的位置,要求推出一個字典序最小的母串。
思路好巧妙,如果暴力往裡面塞的話,肯定會超時,因為有很多重複的位置。所以正解用並查集將已經填充的位置往前並,這樣在下一次填充的時候就會直接到未填充的地點了。
學習新姿勢了。

#include<iostream>
int n,f[3000007],k,mx=0;
char s[3000007],s1[3000007];
int gf(int x){
    return f[x] == x?x:f[x] = gf(f[x]); 
}
int
main() { for(int i=1;i<=3000000;++i) f[i]=i; for(scanf("%d",&n);n;--n){ scanf("%s%d",s1,&k); int len=strlen(s1); for(int x;k;--k){ scanf("%d",&x); for(int i=gf(x);i<x+len;i=f[i]=gf(i+1)){ s[i]=s1[i-x]; if
(i>mx)mx=i; } } } for(int i=1;i<=mx;++i)putchar(s[i]?s[i]:'a');puts(""); return 0; }

B. Crossword solving
題意:給你兩個串,可以將第一個串中的字元更換成任意字元使得第一個串是第二個串的子串,問更改的最小次數。
思路:還是太菜了,沒有第一時間暴力。。

#include <bits/stdc++.h>

using namespace std;

const int maxn = 3000 + 5;
const
int inf = 0x3f3f3f3f; char s[maxn]; char t[maxn]; int main() { int a, b; scanf("%d%d", &a, &b); scanf("%s%s", s, t); int ans = inf; int flag = -1; for (int i = 0;i <= b - a;i++) { int tmp = 0; for (int j = 0;j<a;j++) { if (s[j] != t[j + i]) tmp++; } if (tmp<ans) { ans = min(ans, tmp); flag = i; } } printf("%d\n", ans); for (int i = 0;i<a;i++) if (s[i] != t[flag + i]) printf("%d ", i + 1); puts(""); return 0; }

C. Hacker, pack your bags!
題意:給你n段的始末位置及花銷,要求你找出互不相交的兩段,兩段長度相加等於x,並且花銷最小。
思路:技巧十足啊。。將這n段分別按照左端點排序和右端點排序。然後列舉左端點的序列,求所有右端點小於其左端點的段的最小花銷。這樣最小花銷可以重複使用,複雜度大大減少。

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
const int maxn = 2e5 + 5;
struct node
{
    int l, r, cost;
}a[maxn], b[maxn];

bool cmp1(node a, node b)
{
    if(a.l!=b.l)
    return a.l < b.l;
    return a.r < b.r;
}
bool cmp2(node a, node b)
{
    if (a.r != b.r)return a.r < b.r;
    return a.l < b.l;
}
map<int, int>M;
int main()
{
    int n, x;
    cin >> n >> x;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].cost);
        b[i] = a[i];
    }
    sort(a + 1, a + 1 + n, cmp1);
    sort(b + 1, b + 1 + n, cmp2);
    int i = 1;
    int ans = 2e9+1;
    for (int j = 1;j <= n;j++)
    {
        while (b[i].r<a[j].l)
        {
            if (M.count(b[i].r - b[i].l + 1))
            {
                M[b[i].r - b[i].l + 1] = min(M[b[i].r - b[i].l + 1], b[i].cost);
            }
            else
                M[b[i].r - b[i].l + 1] = b[i].cost;
            i++;
        }
        if (M.count(x - (a[j].r - a[j].l + 1)))
            ans = min(ans, M[x - (a[j].r - a[j].l + 1)]+a[j].cost);
    }
    if (ans == 2e9+1)cout << -1 << endl;
    else cout << ans << endl;
    return 0;

}

D. Mister B and PR Shifts
題意:給你一個序列p,該序列的值為所有的元素與下標相減的絕對值的和。
也就是i=1i=n|p[i]i|
現在可以將後面的元素放到前面來,每次改變都有一個編號。
k=0,序列為p1,p2,…pn
k=1,序列為pn,p1,p2…pn-1
k=2,序列為pn-1,pn,p1…pn-2.
現在問k為多少時序列的值最小。
思路:思路好難想。。一開始猜想是改變後逆序對數最小時序列值最小,這樣做了wa了。。
正確思路:由一個序列變成下一個序列它的值如何變化,我們從這裡下手。
記第k個序列的值為sum[k],那麼第k+1項的值sum[k+1]為多少呢。先拋開最後一個不談,sum[k+1]的值等於sum[k]加上序列中元素值比下標小於等於的個數減去序列中元素值比下標大的個數(重點)。最後再加上最後一個值的影響。
經過變化後,每個數的下標都會變化,除了最後一個其他的都變大了。
一開始,我們可以用(p[i]+n-i)%n(重點)來計算該值到達下標為p[i]位置的改變的編號,當編號大於這個後,p[i]這個元素一定是比下標小的(除了最後一個)。所以我們要將序列中元素值比下標小於等於的個數和大於的個數該改變。
程式碼如下:

#include<iostream>
using namespace std;
const int maxn = 1e6 + 5;
int cf[maxn];
int p[maxn];
typedef long long ll;

int main()
{
    int n;
    cin >> n;
    ll sum = 0;
    //int temp;
    int L = 0, R = 0;//L為元素值比下標大的個數,R則為比下標小於等於的個數。
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &p[i]);
        if (p[i] <= i)R++;
        else L++;
        cf[(p[i] + n - i) % n]++;
        sum += abs(p[i] - i);
    }
    ll ans = sum;int op = 0;
    for (int i = 1;i < n;i++)
    {
        sum = sum - L;
        sum = sum + R - 1;
        sum = sum - abs(p[n - i + 1] - n) + abs(p[n - i + 1] - 1);
        L = L - cf[i] + 1;//想一想這裡為什麼要加1,這裡減去的是當前操作完由大於變成等於的個數,最後一個數不管是不是等於1,p[n-i+1]如果等於1,那麼cf[i]中包括它那個1,所以減去,如果不等於1,就要加上這個變大的數(不等於1肯定大於1)。
        R = R + cf[i] - 1;
        if (ans > sum)
        {
            ans = sum;
            op = i;
        }
    }
    printf("%lld %d\n", ans, op);
    return 0;
}

C. An impassioned circulation of affection
題意:給你一個字串,有q個詢問,詢問由一個數字m和一個字元a組成,你可以將字串的字元變成字元a m次,問字串連續的a最多有多少個。
思路:一道考技巧的暴力題目。。
純暴力的話計算會超時,因為有許多重複的計算,那麼我們計算可以從p到某個位置i剛好改變了m次,當i再增加,p就在當前位置往前走,直到剛好又用m次。
程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    string a;
    cin>>n>>a;
    int q;
    cin>>q;
    char s[5];
    int t;
    for(int i=1;i<=q;i++)
    {
        scanf("%d %s",&t,s);

        int p=0,l=0,ans=-1;
        for(int j=0;j<n;j++)
        {
            if(a[j]!=s[0])l++;
            while(l>t)
            {
                if(a[p]!=s[0])l--;
                p++;
            }
            ans=max(ans,j-p+1);
        }
        printf("%d\n",ans);
    }
    return 0;
}

挺好的題。。
C. The Tag Game
題意:
給你一棵樹,A在1位置,B在x位置,B可以走或者不動,問A和B總共需要進行多少次操作會在同一個位置,B先行。
思路:就喜歡這種考思維的題。。盲目的走其實很難得出答案,我們換個方向思考,看哪些點是滿足題意的,如果一個點到A的距離比到B的距離大,那麼這個點肯定是滿足題意的,如果小,A先經過它,肯定是不滿足題意的。再看等於,等於這種情況有一個坑,直覺上是滿足題意的,但某些是不符合的(讀者可以畫下圖),而符合題意的也不是最優解,所以最好的做法就是直接捨棄等於的情況。
程式碼如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>V[200005];
int d1[200005], d2[200005];
void dfs1(int u, int pre, int d, int *d3)
{
    d3[u] = d;
    for (auto it : V[u])
    {
        if (it == pre)continue;
        dfs1(it, u, d + 1, d3);
    }
}
int main()
{
    int n, x;
    cin >> n >> x;
    int a, b;
    for(int i=1;i<n;i++)
    {
        scanf("%d %d", &a, &b);
        V[a].push_back(b);
        V[b].push_back(a);
    }
    dfs1(1, 0, 0,d1);
    dfs1(x, 0, 0, d2);
    int ans = -1;
    for(int i=1;i<=n;i++)
        if (d1[i] > d2[i])
        {
            ans = max(ans, 2 * d1[i]);
        }
    cout << ans << endl;
}

C. Vladik and Memorable Trip
題意:
將序列分成若干塊,相同元素必須在同一塊,求每一塊不同元素異或相加的最大值是多少。
思路:dp,dp[i]代表從1到i分成若干塊最大值為多少,那麼可以從1到i列舉將1到i分成1到j和j到i按照題意取最值
即可。
程式碼如下:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[5005];
bool vis[5005];
int a[5005];
int sum[5005];
int main()
{
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &a[i]);
        sum[a[i]]++;
    }
    for (int i = 1;i <= n;i++)
    {
        memset(vis, 0, sizeof(vis));
        int temp = 0;
        int cnt = 0;
        for (int j = i;j >= 1;j--)
        {
            if (!vis[a[j]])
            {
                vis[a[j]] = 1;
                temp ^= a[j];
                cnt += sum[a[j]];
            }
            dp[i] = max(dp[i], dp[j - 1] + (--cnt ? 0 : temp));
        }
    }
    cout << dp[n] << endl;
    return 0;
}

C. Maximum splitting
題意:有q個詢問,問n最多可以用多少個合數組成。
思路:最好用4來組成,這樣合數最多。
k=n/4,xx=n%4.
如果xx=0直接輸出k
xx=1 如果k>=2,那麼可以用2個4和1個1組成9 輸出k-1,否則輸出-1
xx=2 k>=1時直接將1個4變成6否則輸出-1
xx=3 k>=3將1個4變成6,2個4相加再加1變成9 輸出k-1,否則輸出-1.

#include<iostream>
using namespace std;
int main()
{
    int q;
    cin >> q;
    while (q--)
    {
        int n;
        cin >> n;
        int k = n / 4;
        int xx = n % 4;
        if (xx == 0)
        {
            cout << k << endl;
        }
        else if (xx == 1)
        {
            if (k >= 2)cout << k - 1 << endl;
            else cout << -1 << endl;
        }
        else if (xx == 2)
        {
            if (k >= 1)cout << k << endl;
            else cout << -1 << endl;
        }
        else
        {
            if (k >= 3)cout << k - 1 << endl;
            else cout << -1 << endl;
        }
    }

}

C. Naming Company
題意:有兩個人,各有一個字串,現在每個人每一次可以將自己的字串中的一個字元放進ans字串沒有被放的位置中。第一個人想最終的ans字典序儘可能小,而另一個人希望儘可能大。第一個人先手,問最終ans是什麼?
思路:好題,要考慮的條件比較多。首先把A字串從小到大排序,B字串從大到小排序。
如果輪到了A放,A的最小字元如果比B的最大字元小的話,那麼A肯定把自己的放越前越好,但如果比B的最大字元還要大呢?這時候A肯定想的讓B的放前面最好,所以A得放在最後面,而且還得放最大的那個(可以想想為什麼)。
同理輪到B放也是這樣的思路.
程式碼如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

char s1[300005], s2[300005];
char ans[300005];
bool cmp1(char a, char b)
{
    return a > b;
}

int main()
{
    cin >> s1 >> s2;
    int len = strlen(s1);
    sort(s1, s1 + len);
    sort(s2, s2 + len, cmp1);
    int begin1 = 0, begin2 = 0, end1 = (len - 1) / 2, end2 = len % 2 ? end1 - 1 : end1;
    int now = 0,end=len-1;
    int flag = 1;
    for (int i = 1;i <= len;i++)
    {
        if (flag == 1)
        {
            flag = 2;
            if (s1[begin1] < s2[begin2])
                ans[now++] = s1[begin1++];
            else
            {
                ans[end--] = s1[end1--];
            }
        }
        else
        {
            flag = 1;
            if (s1[begin1] < s2[begin2])
                ans[now++] = s2[begin2++];
            else
                ans[end--] = s2[end2--];
        }
    }
    for (int i = 0;i < len;i++)
        printf("%c", ans[i]);
    return 0;
}

A. Carrot Cakes
題意:現在需要做n個蛋糕,現在有一個鍋爐,他可以花t時間做k個。你可以選擇再做一個鍋爐,這個需要花費d時間,問是否需要再做一個鍋爐,如果時間能減少就需要,否則不需要。
思路:一般思路計算出t1和t2,來比較兩者大小,但t2不太好算,我們換種思路。
很容易得出第一種方式t1=((n-1)/k+1)*t。所以如果t1 < d那肯定選第一種,但這不是極限情況,試想在用第一個鍋爐做蛋糕時,時間已經超過了d,而蛋糕數還是少k以上個,那麼肯定要做鍋爐。所以極限情況看t1-t是不是大於d,如果大於則需要做鍋爐,否則不需要。

#include <iostream>
int main(){
int n,t,k,d;
std::cin>>n>>t>>k>>d;
std::cout<<((n-1)/k*t>d?"YES":"NO");
}

C. Success Rate
題意:給出你的A題數x和總交題數量y,現在你想要把你成功A題的比率變成p/q,問你最少交幾次比率可以變成p/q。
思路:一開始將題目轉化成了求exgcd,但越求越麻煩。。
換一種思路,要它變成p/q,那麼也就是讓它變成(p*k)/(q*k),可以想象,我們找到一個k滿足題意,那麼比k大的數也能滿足題意,所以可以發現它有單調性。所以採用二分的方式來求出最小的k,滿足check的條件是p*k>=x&&q*k-p*k>=y-x.
特殊情況的條件也很容易的想出來。
程式碼如下:

#include<iostream>
using namespace std;
typedef long long ll;

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        ll x, y, p, q;

        cin >> x >> y >> p >> q;
        if (p == q&&x == y || !p && !x)cout << 0 << endl;
        else if (p == q&&x != y || !p&&x)cout << -1 << endl;
        else
        {
            ll l = 1, r = 1e9;
            ll mid;
            while (l < r)
            {
                mid = l + r >> 1;
                if (p*mid >= x && (q - p)*mid >= (y - x))
                    r = mid;
                else
                    l = mid + 1;
            }
            cout << q*l - y << endl;
        }
    }
}

C. Fountains
題意:有n種噴泉,每個有它的價值和魅力值,每一個噴泉只能用硬幣買或者只能用鑽石買。問Arkady買兩個噴泉,最大魅力值為多少?
樹狀陣列維護價值中最大魅力。然後列舉n種噴泉。主要是列舉方法沒想出來,竟然只需要列舉當前點之前的噴泉。。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100010;
int C[maxn], D[maxn];
void add(int *cf, int p, int b)
{
    while (p<maxn)
    {
        cf[p] = max(cf[p], b);
        p +=  p&-p;
    }
}
int query(int *cf, int p)
{
    int ans = -1;
    while (p)
    {
        ans = max(ans, cf[p]);
        p -= p&-p;
    }
    return ans;
}
const int inf = 0x3f3f3f3f;
int main()
{
    int n, c, d;
    cin >> n >> c >> d;
    int b, p;
    char s[3];
    int ans = 0;
    memset(C, -inf, sizeof(C));
    memset(D, -inf, sizeof(D));
    for (int i = 1;i <= n;i++)
    {
        int ret;
        scanf("%d %d %s", &b, &p, s);
        if (s[0] == 'C')
        {
            if (p > c)continue;
            ret = max(query(C, c - p), query(D, d));
            add(C, p, b);
        }
        else
        {
            if (p > d)continue;
            ret = max(query(C, c), query(D, d - p));
            add(D, p, b);
        }
        if (ret == -1)continue;
        ans = max(ans, ret + b);
    }
    cout << ans << endl;
}

C. Berzerk
題意:有n個點圍成一個圈,1這個點有一個黑洞,每個人有一個set,在他的回合可以讓怪獸走他set裡面的任意一個數的步數,如果走到了黑洞他就贏了。輸出怪獸在每個點A先手的狀態和B先手的狀態。有三種狀態必勝,必敗,迴圈。
思路:一開始以為要求什麼sg函式,然而自己不會sg函式,沒想到就是一個迴圈,用那兩個博弈論準則就行了。
1,一個點能到自己必勝的點,那麼這個點也是必勝點。
2,一個點不管走哪都是必敗,那麼這個點就是必敗。
程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int st[2][7005];
int len[2][7005];
int cnt[2];
typedef pair<int,int>Pii;
queue<Pii>Q;
int main()
{
    int n;
    cin>>n;
    //int cntr,cntm;
    cin>>cnt[0];
    for(int i=1; i<=cnt[0]; i++)
        scanf("%d",&len[0][i]);
    cin>>cnt[1];
    for(int i=1; i<=cnt[1]; i++)
        scanf("%d",&len[1][i]);
    st[0][1]=-2,st[1][1]=-2;
    Q.push(make_pair(0,1));
    Q.push(make_pair(1,1));
    while(!Q.empty())
    {
        Pii temp=Q.front();
        Q.pop();
        int per=temp.first;
        int where=temp.second;
        int lastper=per^1;
        for(int i=1; i<=cnt[lastper]; i++)
        {
            int lastwhere=where-len[lastper][i];
            if(lastwhere<=0)lastwhere+=n;
            if(st[per][where]==-2)
            {
                if(st[lastper][lastwhere]!=-1)
                    st[lastper][lastwhere]=-1,Q.push(make_pair(lastper,lastwhere));
            }
            else
            {
                if(st[lastper][lastwhere]<0)continue;
                st[lastper][lastwhere]++;
                if(st[lastper][lastwhere]==cnt[lastper])
                {
                    st[lastper][lastwhere]=-2;
                    Q.push(make_pair(lastper,lastwhere));
                }
            }
        }
    }
    for(int i=2;i<=n;i++)
    {
        if(st[0][i]==-2)
            printf("Lose ");
        else if(st[0][i]==-1)
            printf("Win ");
        else
            printf("Loop ");
    }
    cout<<endl;
    for(int i=2;i<=n;i++)
    {
        if(st[1][i]==-2)
            printf("Lose ");
        else if(st[1][i]==-1)
            printf("Win ");
        else
            printf("Loop ");
    }
    return 0;
}

C. Molly’s Chemicals
題意:長度為n的序列中,找出所有區間和為k的冪的個數。
思路:處理字首和,列舉k的冪。。關鍵沒想到map。。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[100005];
ll ans=0;
 int n,k;
ll thesum[100005];
void solve(ll temp)
{
    map<ll,int>M;
    M[0]=1;
    for(int i=1;i<=n;i++)
    {
        M[thesum[i]]++;
        if(M.count(thesum[i]-temp))
            ans+=M[thesum[i]-temp];
    }
}
int main()
{

    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        thesum[i]=thesum[i-1]+a[i];
    }
    if(k==1)
    {
        solve(1);
        cout<<ans<<endl;
    }
    else if(k==-1)
    {
        solve(1);
        solve(-1);
        cout<<ans<<endl;
    }
    else
    {
        ll temp=1;
        while(temp<=1e9*n)
        {
            solve(temp);
            temp=temp*k;
        }
        cout<<ans<<endl;



    }


}