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 ) (1≤n≤1051,≤m≤2×105,1≤q≤106)
问每个学生最终接收到的消息数量(自己发送的消息,不算自己接收)
思路:讲道理读懂题意后是懵的。这么多更新,这么多的群,一个人又可以在多个群里。完全不知道怎么维护。
- 其实仔细分析一下可以知道,其实并不是每次都需要立即更新的。可以对每一个群,打一个 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) (1≤n,m≤3×105,1≤ai≤109)
思路:
-
从结果的角度出发,假设有 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 ai∑j=1i−1aj+n−i+1≥m ,那么 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 i∈A ,增加 a i a_i ai 的分数,对于任意二元组 { i , j } \{i,j\} {i,j} 满足 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) (1≤n≤105,1≤ai,bi≤109)
思路:
- 根据幂可以这样分开来看: 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;
}