Codeforces Round #431 (Div. 2)(有E题!)比赛总结

做完这套题感觉如释重负。。只有一种感觉:好难,连前2道都让我感觉很困难。单看代码其实不怎么样,但它的思路确实让我感到了和大佬的差距。
A题题意:给你个数为n的序列,让你将该序列分成若干个小序列,小序列开头,结尾,个数都必须为奇数,并且,小序列的总个数也必须为奇数。问你是否有可能。
思路:博主的第一想法是模拟。。然而仔细想想该题有其奥妙在内。试想,小序列元素个数是奇数个,而小序列总个数也是奇数个,那么原本的序列总个数必然是奇数个,所以如果n为偶数,则必然输出NO(哭),然后如果n为奇数,只需判断第一个数和最后一个数是不是奇数,如果不是奇数,那么肯定拿不完,如果都是奇数,那么直接全部拿走,满足题意。
这题差点没做出来,唉。。
代码如下:

#include<iostream>
using namespace std;
#pragma warning (disable:4996)
int main()
{
    int n;
    scanf("%d", &n);
    int x;
    bool flag = true;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &x);
        if (i == 1 || i == n)
        {
            if (x % 2 == 0)flag = false;
        }
    }
    if (flag&&n % 2)
        puts("YES");
    else  puts("NO");
    return 0;
}

B题题意:给你n个点的纵坐标,他们的横坐标依次为1到n,问这n个点是否能组成两条平行的线。
思路:前三个点1,2,3必然有两个在一条直线上,所以分3类,然后删掉在该直线上的点,再判断其他的点是不是能组成一条与该直线平行的线。

#include<iostream>
#include<cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 1005;
long long y[maxn];
bool vis[maxn];
int n;
void solve(long long k,int temp)
{
    long long    yy = y[temp];
    vis[temp] = 1;
    for (int i = 1;i <= n;i++)
    {
        if (1LL*2 * (y[i]-yy) == k*(i-temp))
            vis[i] = 1;
    }
}

bool check()
{
    for (int i = 1;i <= n;i++)
        if (!vis[i])return 0;
    return 1;
}
int main()
{

    scanf("%d", &n);
    for (int i = 1;i <= n;i++)
    {
        scanf("%lld", &y[i]);
    }
    long long k[4];
    k[1] = 2 * (y[2] - y[1]);
    k[2 ]= (y[3] - y[1]);
    k[3] = 2 * (y[3] - y[2]);
    for (int i = 1;i <= 3;i++)
    {
        memset(vis, 0, sizeof(vis));
        if (i == 1 || i == 2)
            solve(k[i], 1);
        else
            solve(k[i], 2);
        if (check())
        {
            puts("NO");
            return 0;
        }
        int temp = -1;
        bool flag = true;
        for(int j=1;j<=n;j++)
            if (vis[j] == 0)
            {
                if (temp == -1)
                    temp = j;
                else
                {
                    if (1LL * 2 * (y[j] - y[temp]) != k[i] * (j - temp))
                    {
                        flag = false;
                        break;
                    }
                }
            }
        if (flag)
        {
            puts("YES");
            return 0;
        }
    }
    puts("NO");
    return 0;
}

这题在写的时候为了避免浮点数(斜率),博主在分类的时候特地把3个斜率都乘以2,类似的小细节读者可以学习学习~
C题题意:给你一个正整数k,要求你组成一个字符串,并且组成该字符串的最小花费为k,输出该字符串。
组成字符串的花费是这样算的:
比如要组成abababab, 它可以拆成s=”abab”和t=”abab”,那么把这两个小字符串拼接的花费为
c(a,b,c..z) f(s,c)*f(t,c)
f(s,c)的值为c在s中出现的次数。
思路:其实仔细想想就发现,每个字符串的值其实没有大小之分,是一个固定的值。那么我们只要想一个通用的方法去构造就好。
很容易想到先一直输出a,那么花费cost=1+2+3+4…,直到超过k,我们再用b,同样一直输出b。。。
这个方法是没问题的,然而需要值得注意的是,当输入0的时候要输出一个a,不然会WA3(哭)

#include<iostream>
using namespace std;
int n;
void solve()
{
    //char tempc = 'a';
    int cnt = 0;
    int temp = 0;
    for (char tempc = 'a';tempc <= 'z';tempc++)
    {
        if (cnt == n)break;
        printf("%c", tempc);
        temp = 0;
        while (cnt < n)
        {
            temp++;
            if (cnt + temp > n)break;
            cnt += temp;
            printf("%c", tempc);
        }
    }
    cout << endl;
}

int main()
{

    cin >> n;
    if (n == 0) { cout << "a" << endl;return 0; }
    solve();
    return 0;
}

D题题意:分别有从横坐标,纵坐标开始出发的点,如果两个点会相撞,两个点的方向会交换,给你两个边界(上边界和右边界),问最终所有的点会到哪里去。。
思路:博主的第一思路竟然又是模拟。。
因为博主曾经做过一道差不多的题,也是很多点运动,首先计算出所有点相撞的时刻,然后把时间从小到大排序,然后到那个时刻就将两个点的编号交换即可,博主试着写了下,其实可行,就是点太多了,在第9个样例TLE了。试想如果横坐标有50000个点,纵坐标有50000个点,每个点都可以相撞,那么就有50000*50000个事件,很可怕。。超时是必然的。
暴力不可行,那么怎么办呢,其实只要在图纸上把题目给的样例模拟一遍,就会发现规律了,读者可以结合代码观察观察。
代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 2e5 + 5;
struct node
{
    int idx, p;
    bool operator<(const node &b)const
    {
        return p < b.p;
    }
}ver[100005], hor[100005];
int vcnt, hcnt;
vector<node>V1[maxn], V2[maxn];
int tempx[100005], tempy[100005];
int ansx[100005], ansy[100005];
int main()
{
    int n, w, h;
    cin >> n >> w >> h;
    int g,p,t;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &g);
        if (g == 1)
        {
            vcnt++;
            scanf("%d%d", &p, &t);
            V1[p - t + 100000].push_back({ i,p });
        }
        else
        {
            hcnt++;
            scanf("%d%d", &p, &t);
            V2[p - t + 100000].push_back({ i,p });
        }
    }
    for (int i = 0;i < maxn;i++)
    {
        sort(V1[i].begin(), V1[i].end());
        sort(V2[i].begin(), V2[i].end());
    }

    for (int i = 0;i < maxn;i++)
    {
        int cnt = 0;
        for (int j = 0;j < V1[i].size();j++)
        {
            tempx[cnt] = V1[i][j].p;
            tempy[cnt] = h;
            cnt++;
        }
        for (int j = V2[i].size() - 1;j >= 0;j--)
        {
            tempx[cnt] = w;
            tempy[cnt] = V2[i][j].p;
            cnt++;
        }
        cnt = 0;
        for (int j = V2[i].size() - 1;j >= 0;j--)
        {
            ansx[V2[i][j].idx] = tempx[cnt];
            ansy[V2[i][j].idx] = tempy[cnt];
            cnt++;
        }
        for (int j = 0;j < V1[i].size();j++)
        {
            ansx[V1[i][j].idx] = tempx[cnt];
            ansy[V1[i][j].idx] = tempy[cnt];
            cnt++;
        }
    }

    for (int i = 1;i <= n;i++)
    {
        printf("%d %d\n", ansx[i], ansy[i]);
    }
    return 0;

}

E题题意:给你n个数,数的值代表该点的颜色,有m个询问
如果询问为1 p x则是把p位置的点的值变成x
如果是2 l r 则是询问l到r的”memory”,memory计算如下.
如果序列为1 2 3 1 3 2 1
询问2 3 7
从3到7 为1的终点减起点也就是7-4=3
2:6-6=0
3:5-3=2
所以memory为5
思路:这题需要转换一下,消除差异性,也就是颜色带来的区别。如果[l,r]中颜色为x的位置有p[1],p[2],p[3]…p[k],令p[1] < p[2]< p[3]< …< p[k],则该序列颜色x提供的”memory”为p[k]-p[1],而p[k]-p[1]=(p[k]-p[k-1])+(p[k-1]-p[k-2])+…+(p[2]-p[1]).
所以我们可以建立一个pair b,b[i]=(prev[i],i-prev[i]).
prev[i]=maxj (1<=j< i&&a[j]=a[i])
那么我们要求的是
i[l,r],b[i].first>=l b[i].second
这个式子读者可以理解一下,其实很简单。关键怎么写。线段树?那么怎么建树?不管怎么样,线段树只能保证一个区间中的值,可是这个求和有两个区间啊。
这里就要写个树套树了。
第一层区间用权值线段树动态开点
第二层区间用树状数组做。
所以它的含义到底是啥?
博主的理解就是:以b[i].first为pre的点中建一个线段树,线段树中维护b[i].second的和,也就是我们要求的。
询问则是query(v,v)-query(u-1,v),该式子表示的就是以[u,v]中任意一个为pre的点中的1到v的权值。为什么是1到v而不是u到v?因为以u到v为pre的值全部都大于u,所以1到u的点实际上对这个权值没有贡献的,所以这样求的结果与实际答案是一样的。
代码如下:

#include<iostream>
#include<set>
using namespace std;
const int maxn = 100005;
const int maxm = 10000005;
int a[maxn];
set<int>S[maxn];
#define lowbit(x) ((x)&(-x))
typedef long long ll;
int rt[maxn];
int n, m;
int Left[maxm], Right[maxm];
int cnt;
ll Sum[maxm];
void Update(int L, int C, int l, int r, int &tr)
{
    if (!tr)
        tr = ++cnt;
    Sum[tr] += C;
    if (l == r)return;
    int mid = l + r >> 1;
    if (L <= mid)Update(L, C, l, mid, Left[tr]);
    else Update(L, C, mid + 1, r, Right[tr]);
}

void modief(int pre, int now, int d)
{
    while (pre<=maxn)
    {
        Update(now, d, 1, n, rt[pre]);
        pre += lowbit(pre);
    }
}
void add(int col, int idx)
{

    S[col].insert(idx);
    auto it = S[col].find(idx),pre=it,suf=it;
    bool flag1 = false, flag2 = false;
    if (S[col].begin() != it)
    {
        pre--;
        modief(*pre, *it, *it - *pre);
        flag1 = true;
    }
    suf++;
    if (S[col].end() != suf)
    {
        modief(*it, *suf, *suf - *it);
        flag2 = true;
    }
    if (flag1&&flag2)
    {
        modief(*pre, *suf, -(*suf - *pre));
    }
}
void del(int idx)
{
    int col = a[idx];
    auto it = S[col].find(idx), pre = it, suf = it;
    bool flag1 = false, flag2 = false;
    if (it != S[col].begin())
    {
        pre--;
        modief(*pre, *it, -(*it - *pre));
        flag1 = true;
    }
    suf++;
    if (suf != S[col].end())
    {
        modief(*it, *suf, -(*suf - *it));
        flag2 = true;
    }
    if (flag1&&flag2)
    {
        modief(*pre, *suf, *suf - *pre);
    }
    S[col].erase(*it);
}

ll ask(int L, int R, int l, int r, int &rt)
{
    if (!rt || l == L&&r == R)return Sum[rt];
    int mid = l + r >> 1;
    if (R <= mid)return ask(L, R, l, mid, Left[rt]);
    if (L > mid)return ask(L, R, mid + 1, r, Right[rt]);
    return ask(L, mid, l, mid, Left[rt]) + ask(mid+1, R, mid + 1, r, Right[rt]);
}
ll query(int a, int b)
{
    ll ret = 0;
    while (a)
    {
        ret += ask(1, b, 1, n, rt[a]);
        a -= lowbit(a);
    }
    return ret;
}

int main()
{

    cin >> n >> m;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &a[i]);
        add(a[i], i);
    }
    int o, x, y;
    for (int i = 1;i <= m;i++)
    {
        scanf("%d%d%d", &o, &x, &y);
        if (o == 1)
        {
            del(x);
            a[x] = y;
            add(y, x);
        }
        else
        {
            printf("%lld\n", query(y, y) - query(x - 1, y));

        }
    }
    return 0;
}

这题只能说很难,博主虽然搞清楚了,但可能讲不清楚,如果没明白有疑问的话,欢迎留言。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值