【题解】CF1824 合集
CF1824A LuoTianyi and the Show
标签:思维题 \(C\)
我们可以较为容易地得出一个贪心策略,就是先去放一个以第 \(3\) 中方式入座的人,再在两边放 \(1,2\) 种方式的人,如果放的时候占用了第三种方式的人的座位就跳过该座位,最后将剩下的以第 \(3\) 中方式入座的人放进去。
当然还有可能只放 \(1,3\) 或 \(2,3\) 种方式的人
至于现将哪个以第三种方式入座的人放进去,我们可以进行枚举,容易地,我们可以 \(O(1)\) 求得以每一个座位开始的答案。
code:
#include<bits/stdc++.h>
using namespace std;
const int NN = 1e5 + 8;
int t,n,m;
int a[NN];
int vis[NN];
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
int cnt1 = 0,cnt2 = 0,cnt3 = 0;
int ans1 = 0,ans2 = 0,ans3 = 0;
memset(a,0,sizeof(a));
memset(vis,0,sizeof(vis));
for(int i = 1; i <= n; ++i){
scanf("%d",&a[i]);
if(a[i] > 0){
if(vis[a[i]] == 0)
vis[a[i]] = 1,++cnt3;
}
else if(a[i] == -1) ++cnt1;
else ++cnt2;
}
ans1 = min(cnt1 + cnt3,m);
ans2 = min(cnt2 + cnt3,m);
for(int i = 1; i <= m; ++i) vis[i] += vis[i-1];
for(int i = 1; i <= m; ++i)
if(vis[i] - vis[i-1] > 0)
ans3 = max(ans3,min(cnt1,i-1-vis[i-1]) + min(cnt2,m-i-(vis[m]-vis[i]))+cnt3);
printf("%d\n",max(ans1,max(ans2,ans3)));
}
}
CF1824B2 LuoTianyi and the Floating Islands (Hard Version)
标签:思维题 \(B\)
我们从 Easy Version 中可以进行一个猜测,并得到结论:
- \(k \%2 = 1\) 的时候,我们可以发现答案就是 \(1\)
那么 \(k\%2 = 0\) 的时候怎么做呢?
我们其实可以枚举边,对于每条边,我们在其两边各放 \(\frac k 2\) 个点的方案数,就是我们的答案。
但是,因为我们的边数是比点数少 \(1\) 的,所以说好点的期望数是边的期望数 \(+1\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8,MOD = 1e9 + 7;
int n,k;
int siz[NN];
ll ans = 0;
ll fac[NN],inv[NN];
ll ksm(ll x,ll k){
ll res = 1;
while(k){
if(k & 1) res = res * x % MOD;
x = x * x % MOD;
k >>= 1;
}
return res;
}
ll binom(ll n,ll m){
if(n < m) return 0;
return fac[n] * inv[m] % MOD * inv[n-m] % MOD;
}
ll invb(ll n,ll m){
return inv[n] * fac[m] % MOD * fac[n-m] % MOD;
}
struct Edge{
int to,next;
}edge[NN << 1];
int head[NN],cnt;
void init(){
memset(head,-1,sizeof(head));
cnt = 1;
fac[0] = 1;
for(int i = 1; i <= n; ++i) fac[i] = fac[i-1] * i % MOD;
inv[n] = ksm(fac[n],MOD - 2);
for(int i = n; i >= 1; --i) inv[i-1] = inv[i] * i % MOD;
}
void add_edge(int u,int v){
edge[++cnt] = {v,head[u]};
head[u] = cnt;
}
void dfs(int u,int fa){
siz[u] = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
dfs(v,u);
siz[u] += siz[v];
ans = (ans + binom(siz[v],k/2) * binom(n-siz[v],k/2)) % MOD;
}
}
int main(){
scanf("%d%d",&n,&k);init();
if(k & 1){puts("1");return 0;}
for(int i = 1,u,v; i < n; ++i){
scanf("%d%d",&u,&v);
add_edge(u,v);add_edge(v,u);
}
dfs(1,1);
ans = (ans * invb(n,k) + 1) % MOD;
printf("%lld",ans);
}
CF1824C LuoTianyi and XOR-Tree
标签:DP \(B\)
我们可以证明,如果需要异或,取一个点的子树内异或次数更少的一定不会更劣。
prove:
如果说在一个点处取次数更多的方案,在它的父亲处最多只会减少该点所在子树的一次异或,所以取最少的次数的方案一定不会更劣。
因为我们已经证明了每次去子树内异或次数更小的,一定不会更劣,所以我们可以用 DP 解决异或次数的问题。
我们设 \(f_u\) 表示以 \(v\) 为根的子树,最小的异或次数,\(cnts_u\) 记为 \(u\) 的儿子个数,\(maxs_u\) 记为 \(u\) 的所有儿子中出现最多的异或值的出现次数。
然而,我们最小异或次数相同的方案,显然不能直接在当前子树处理,需要将所有方案给到他的父亲,让他的父亲决定。
所以我们需要对于每个节点开一个 map,并且使用启发式合并对所有方案进行合并。
写启发式合并的时候需要注意:
-
如果说 \(maxs\) 为 \(1\),那么我们就直接将儿子中最大的
map和 父亲的map交换,并且给父亲整体打一个tag(因为此时如果去遍历最大的那个map会使复杂度退化)。 -
如果说 \(maxs\) 大于 \(1\),那么我们就直接去暴力更新父亲的
map(因为如果 \(maxs\) 大于 \(1\),那么这个儿子中最大的map的大小一定是小于 \(\frac {siz_u} 2\) 的,满足启发式合并维持时间复杂度的条件)。 -
如果是叶节点,需要进行特判对
map赋值。
最后一个小知识:swap 交换两个 STL 容器时间复杂度近似 \(O(1)\)。
code:
#include<bits/stdc++.h>
using namespace std;
const int NN = 2e5 + 8;
#define st first
#define ed second
int n;
int a[NN];
struct Edge{
int to,next;
}edge[NN << 1];
int head[NN],cnt;
int tag[NN];
int f[NN];
map<int,int> m[NN];
void init(){
memset(head,-1,sizeof(head));
cnt = 1;
}
void add_edge(int u,int v){
edge[++cnt] = {v,head[u]};
head[u] = cnt;
}
void dfs(int u,int fa){
int maxs = 0,pos = 0,cnts = 0;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
++cnts;
dfs(v,u);
f[u] += f[v];
if(m[v].size() > maxs) maxs = m[v].size(),pos = v;
}
if(cnts == 0) return m[u][a[u]] = 1,void(0);//如果是叶节点,需要进行特判对 map 赋值
maxs = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(v == fa || v == pos) continue;
for(auto it : m[v]) maxs = max(maxs,m[pos][it.st ^ tag[v] ^ tag[pos]] += it.ed);
}
f[u] += cnts - maxs;
if(maxs > 1){
for(auto it : m[pos]){//暴力更新父亲的 map
if(it.ed != maxs) continue;
m[u][it.st ^ a[u] ^ tag[pos]] = 1;
}
m[pos].clear();//防止 MLE
}
else swap(m[u],m[pos]),tag[u] = a[u] ^ tag[pos];//将儿子中最大的 map 和 父亲的 map 交换,并打上标记
}
int main(){
scanf("%d",&n);init();
for(int i = 1; i <= n; ++i) scanf("%d",&a[i]);
for(int i = 1,u,v; i < n; ++i){
scanf("%d%d",&u,&v);
add_edge(u,v);add_edge(v,u);
}
dfs(1,1);
printf("%d",f[1] + (!m[1].count(tag[1])));
}
CF1824D LuoTianyi and the Function
标签: DS \(A^-\)
将询问差分,我们可以得到:
然后就是线段树区间加,历史版本区间和。
code:
写不动了QAQ……
本文来自博客园,作者:ricky_lin,转载请注明原文链接:https://www.cnblogs.com/rickylin/p/CF1824.html

浙公网安备 33010602011771号