思维、模拟、分类讨论习题

2020CCPC威海 H Message Bomb

题意: n 个群 m 个学生,q 个消息,有 3 种类型

  • 1 x y : 学生 x 加入群 y
  • 2 x y :学生 x 退出群 y
  • 3 x y : 学生 x 在群 y 发送一条消息,其他群里的人都会接收到
    ( 1 ≤ n ≤ 1 0 5 1 , ≤ m ≤ 2 × 1 0 5 , 1 ≤ q ≤ 1 0 6 ) (1\le n \le 10^5 1,\le m\le 2\times 10^5 ,1\le q\le 10^6 ) (1n1051,m2×105,1q106)

问每个学生最终接收到的消息数量(自己发送的消息,不算自己接收)

思路:讲道理读懂题意后是懵的。这么多更新,这么多的群,一个人又可以在多个群里。完全不知道怎么维护。

  • 其实仔细分析一下可以知道,其实并不是每次都需要立即更新的。可以对每一个群,打一个 tag 标记它的消息的数量,然后每个人进出群的时候,相应的加减tag 就好了。就是线段树 lazy 的思想
  • 最后再把所有的 tag 累积到答案上就好了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;

int n,m,q;
set<int> s[maxn];
int ans[maxn<<1],tag[maxn];

int main()
{
    scanf("%d%d%d",&n,&m,&q);
    int op,x,y;
    while(q--)
    {
        scanf("%d%d%d",&op,&x,&y);
        if(op==1)
        {
            ans[x]-=tag[y];
            s[y].insert(x);
        }
        else if(op==2)
        {
            ans[x]+=tag[y];
            s[y].erase(x);
        }
        else if(op==3)
        {
            tag[y]++;
            ans[x]--;
        }
    }
    for(int i=1; i<=n; ++i)
    {
        for(auto x: s[i])
            ans[x]+=tag[i];
    }
    for(int i=1; i<=m; ++i)
        printf("%d\n",ans[i]);
    return 0;
}

2020ICPC沈阳 L. Flowers

题意:有 n 种花,每种有 a i a_i ai 朵,现在把 m 朵不同种类的花扎成一束,问最多能够得到几束花。 ( 1 ≤ n , m ≤ 3 × 1 0 5 , 1 ≤ a i ≤ 1 0 9 ) (1\le n ,m \le 3\times 10^5 ,1\le a_i \le 10^9) (1n,m3×105,1ai109)

思路

  • 从结果的角度出发,假设有 k 束花,就相当于有 k 个桶,那么 a i a_i ai 大于 k 的部分,就必须舍弃。其余小于 k 的花,就可以不重复的任意放。

  • 因此将 a i a_i ai 从小到大排序后,枚举 a i a_i ai ,那么如果 ∑ j = 1 i − 1 a j a i + n − i + 1 ≥ m \frac {\sum_{j=1}^{i-1} a_j}{a_i} + n-i+1\ge m aij=1i1aj+ni+1m ,那么 a i a_i ai 就是可取的答案。

  • 按正向的思维来推:把这 n 种花按一定方式分配,然后使得扎成的花束最多。很难想,很难转移。

  • 但是如果倒着推,如果能够扎成 k 束花,就会发现, a i a_i ai 大于 k 的部分是需要舍弃的。其余小于 k 的花,是可以在 k 束花里面每束插一朵的

  • 这样的话,二分或者枚举 a i a_i ai 都可以,是同一个意思

2020ICPC西安 M. Value

链接:https://codeforces.com/gym/102471/problem/M

题意:设 A 是 { 1 , 2 , … , n } \{1,2,\dots,n\} {1,2,,n} 的一个子集。初始分数为 0 ,对每一个 i ∈ A i \in A iA ,增加 a i a_i ai 的分数,对于任意二元组 { i , j } \{i,j\} {ij} 满足 i k = j i^k=j ik=j ( k > 1 ) (k>1) k>1,则减去 b j b_j bj 。问怎样选择可以使得集合 A 的分数最大。这个最大分数是多少。 ( 1 ≤ n ≤ 1 0 5 , 1 ≤ a i , b i ≤ 1 0 9 ) (1\le n \le 10^5,1\le a_i,b_i\le 10^9) (1n105,1ai,bi109)

思路

  • 根据幂可以这样分开来看: i , i 2 , i 3 , … i k i , i^2,i^3,\dots i^k i,i2,i3,ik ,然后需要选择一个子集,使得获得的分数最大。
  • 从后往前贪心:假设选择了 i 8 i^8 i8 ,当遇到 i 4 i^4 i4 的时候,发现 b[i^8] 特别大。此时,是选择保留: i 8 i^8 i8 还是 i 4 i^4 i4 ,我们是无法判断的。
  • 不能贪心,不能DP。没有最优子结构,然后会产生后效性。
  • 所以只能最暴力的方式了:直接枚举子集
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;

int n,a[maxn],b[maxn];
int visit[maxn];

ll solve(int p)
{
    ll cur=p;
    vector<int> vec;
    while(cur<=n)
    {
        vec.push_back(cur);
        visit[cur]=1;
        cur*=p;
    }
    int m=vec.size();
    ll ans=0;
    for(int i=0; i<(1<<m); ++i)
    {
        vector<int> y;
        for(int j=0; j<m; ++j)
            if(i>>j&1) y.push_back(vec[j]);
        int len=y.size();
        ll res=0;
        for(int j=0; j<len; ++j)
        {
            res+=a[y[j]];
            for(int k=j+1; k<len; ++k)
            {
                ll aa=y[j],bb=y[k];
                while(aa<bb) aa*=y[j];
                if(aa==bb) res-=b[bb];
            }
        }
        ans=max(ans,res);
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
    for(int i=1; i<=n; ++i) scanf("%d",&b[i]);
    ll ans=a[1];
    for(int i=2; i<=n; ++i)
        if(!visit[i]) ans+=solve(i);
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值