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=ni=1|p[i]i| ∑ i = 1 i = 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;



    }


}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值