Codeforces Round #426 (Div. 2)比赛总结

这场比赛比上次有进步,做出来2道题,但是由于交的时间太晚,没涨分。。
目前仍然是1456。。
A题题意:给你2个字符,2个字符都是这四个中的一个(v<^>),再给你一个步数k,经过k次旋转,第一个字符可以变成第二个字符,问你他是顺时针还是逆时针还是不可确定的…
思路:只需知道两个字符的位置,然后判断即可(搞不清楚为什么有人模拟做出这道题的。。)

#include<iostream>
#include<algorithm>
using namespace std;
char s[10] = "v<^>";
//char m[10] = "v>^<";
int main()
{
    char a, b;
    cin >> a >> b;
    int times;
    cin >> times;
    times %= 4;
    int thea, theb;
    if (a == 'v')thea = 1;
    else if (a == '<')thea = 2;
    else if (a == '^')thea = 3;
    else thea = 4;
    if (b == 'v')theb = 1;
    else if (b == '<')theb = 2;
    else if (b == '^')theb = 3;
    else theb = 4;
    //int temp = abs(thea - theb);
    bool flag1 = false, flag2 = false;
    if ((thea + times - 1) % 4 + 1 == theb)
        flag1 = true;
    if ((thea - times +3) % 4 + 1 == theb)
        flag2 = true;
    if (flag1 && !flag2)printf("cw\n");
    else if (flag2 && !flag1)printf("ccw\n");
    else printf("undefined\n");
    return 0;
}
//cw 顺时针  ccw逆时针

B题题意:输入一堆大写字母,每个字符从第一次出现到最后一次出现的这段时间内需要一个守卫, 问你在给定k给守卫的条件下,总需求会不会超过k个守卫。。

思路:首先记录每个字符的起始位置和终止位置,然后遍历遇到起始位置,k–遇到终止位置k++,判断k会不会小于0即可。(注意判断顺序)

#include<iostream>
#include<string>
#include<set>
#include<cstring>
using namespace std;
#pragma warning(disable:4996)
int main()
{
    int n, k;
    set<int>begin, End;
    scanf("%d%d", &n, &k);
    string s;
    cin >> s;
    int len = s.size();
    int temp;
    bool vis[26];
    memset(vis, 0, sizeof(vis));
    for (int i = 0;i < len;i++)
    {
        temp = s[i] - 'A';
        if (vis[temp] == 0)
        {
            vis[temp] = 1;
            begin.insert(i);
        }
    }
    memset(vis, 0, sizeof(vis));
    for (int i = len - 1;i >= 0;i--)
    {
        temp = s[i] - 'A';
        if (vis[temp] == 0)
        {
            vis[temp] = 1;
            End.insert(i);
        }
    }
    bool flag = true;
    for (int i = 0;i < len;i++)
    {
        if (begin.count(i))
            k--;
        if (k < 0) 
        {
            flag = false;
            break;
        }
        if (End.count(i))k++;
    }
    if (!flag)printf("YES\n");
    else printf("NO\n");
}

C题题意:有两个人在玩游戏,赢的人可以加k*k分(k是个任意值),而输的人只能加k分,现在,给你他们最终的成绩a,b,问他们的成绩是不是由这个规则变出来的。
思路: 仔细想想,就能想出a*b必定是一个数的立方项。假如m^3=a*b,则必定有a%m=0 b%m=0(不然分配就不均匀了,比如1 64,虽然是立方项,但不满足题意)

#include<iostream>
#include<set>
#include<math.h>
#include<algorithm>
using namespace std;
#pragma warning(disable:4996)
const double eps = 1e-5;
int main()
{
    int n;
    scanf("%d", &n);
    long long a, b;
    for (int i = 1;i <= n;i++)
    {
        scanf("%lld%lld", &a, &b);
        long long temp = a*b;
        bool flag = false;
        long long k = pow(temp, 1.0 / 3)+eps;
        //cout << k << endl;
        if (k*k*k == temp)flag = true;
        if (a%k || b%k)flag = false;

        if (flag)printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

终于到D题了。。。
D题好难。。看别人的题解都看半天。。
D题题意:给你一个长度为n的数组,要你把它切成k份,每一份的权值是该区间内不同数的个数,问你k份权值之和最大是多少?

由于之前的操作会影响到后面的操作,所以得用dp.
易得(很难得到。。)dp方程式为
dp[k][n]=max(dp[k-1][i]+c(i+1,n));(0 = < i<=n-1)
dp[k][n]为把前k项分成n份的最大权值和。c(i,n)表示从i到n的不同数的个数
一般思路。。3层for循环。。第一层k,第二层枚举n,第三层枚举i (依照上面的方程式)
然而会TL
所以我们得想办法优化。。
试想 当我们求dp[i][j]的时候,dp[i][j-1]肯定是求出来的。那么,我们看看dp[i][j]和dp[i][j-1]有什么区别呢。。
dp[i][j]=max(dp[i-1][x]+c(x+1,j))
dp[i][j-1]=max(dp[i-1][x]+c(x+1,j-1))
当我们求dp[i][j]的时候,dp[i-1]的所有项肯定都求出来了,所以不必担心前面。
我们观察式子,两个式子的差别就在c上,前者还包括了第j项,这导致某些第一个式子的c会比第二个的多1,而有些又不变。范围很简单,就是上次出现a[ j ]这个数的位置后边的都要+1,之前都不变。这其实可以看成一个区间操作。
另外第一个式子的x可取到j-1,而第二个式子只能取到j-2,所以第一个式子还多一个dp[i-1][j-1]
所以于是我们可以开一个线段树,在更新dp[ i ][ 1 .. n ]的时候每次进行一个区间操作和一个单点操作。也就是总共nlogn的复杂度。再算上外层枚举k,总体复杂度为knlogn。

for ( i = 1 .. k )
        清空线段树
        for ( j = 1..n)
                type = a[ j ]
                x = a[ j ]上一次出现的位置下表
                线段树区间操作:[ x , j-1 ] +1
                线段树单点操作:[ j-1 ] +dp[ i-1 ][ j-1 ]
                dp[ i ][ j ] = tree [ 1 ] (整个区间中的最大值)
        endfor
endfor

另外,我们会发现再求dp[i][j]的时候只会用到dp[i-1]的值,所以可以用一个滚动数组来优化一下。。虽然没什么卵用。。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#pragma warning(disable:4996)
const int maxn = 35050;
int Sum[maxn << 2], Add[maxn << 2];//Add为懒惰标记
int A[maxn], n;//存原数组[1,n]
void PushUp(int rt) { Sum[rt] = max(Sum[rt << 1], Sum[rt << 1 | 1]); }
void Build(int l, int r, int rt)
{
    if (l == r)
    {
        Sum[rt] = A[l];
        return;

    }
    int m = (l + r) >> 1;
    Build(l, m, rt << 1);
    Build(m + 1, r, rt << 1 | 1);
    PushUp(rt);
}

void PushDown(int rt)
{
    if (Add[rt])
    {
        Add[rt << 1] += Add[rt];
        Add[rt << 1 | 1] += Add[rt];
        Sum[rt << 1] += Add[rt];
        Sum[rt << 1 | 1] += Add[rt];
        Add[rt] = 0;
    }
}


void Update(int L, int C, int l, int r, int rt)//点修改
{
    if (l == r)
    {
        Sum[rt] += C;
        return;
    }
    int m = (l + r) >> 1;
    PushDown(rt);
    if (L <= m)Update(L, C, l, m, rt << 1);
    else Update(L, C, m + 1, r, rt << 1 | 1);
    PushUp(rt);

}


void Update(int L, int R, int C, int l, int r, int rt)
{
    if (L <= l&&r <= R)
    {
        Sum[rt] += C;
        Add[rt] += C;
        return;
    }
    int m = (l + r) >> 1;
    PushDown(rt);
    if (L <= m)Update(L, R, C, l, m, rt << 1);
    if (R > m)Update(L, R, C, m + 1, r, rt << 1 | 1);
    PushUp(rt);
}
int Query()
{
    return Sum[1];
}
int k;
int last[maxn];
int head[maxn];
int dp[2][maxn];
int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &A[i]);
        last[i] = head[A[i]];
        head[A[i]] = i;
    }
    int b = 0;
    for (int i = 1;i <= k;i++,b^=1)
    {
        memset(Sum, 0, sizeof(Sum));
        memset(Add, 0, sizeof(Add));
        for (int j = 1;j <= n;j++)
        {
            Update(last[j],j-1,1,0,n,1);
            Update(j-1, dp[b^1][j-1], 0, n, 1);
            dp[b][j] = Query();
        }

    }
    printf("%d\n", dp[b ^ 1][n]);
    return 0;
}

这道题是真的难。。
直到现在我还是似懂非懂,仍然需要消化。所以如果看完这篇题解还有什么不懂的,可以一起来讨论,欢迎来指正哟。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值