A.White and Black

暴力的 \(O(nq)\) 做法比较显然,因为对于根节点来说,只有它自己可以改变自己的颜色,因此如果它是黑色则一定需要更改自己,同时把更改传下去(应该没有那种每次真的更改所有节点然后写 \(O(nqn^{n})\) 的吧),同理,假如根节点更改结束,次级节点就同理了,这是一个连锁的反应,因此搜一遍下来就行了.

这道题的优化点主要是在减少白点的枚举次数上,注意到数据范围中给出的黑点数量非常少,因此我们考虑将枚举白点变成枚举子节点总数减去黑点总数,这样就能把复杂度降下来.

这样还是不好做,考虑预处理出每个节点的儿子总数,如果节点变黑,则其每个儿子对它作 \(1\) 的贡献,如果儿子也是黑色的,说明该儿子不再对该父节点做贡献,临时减掉即可.

这道题写了部分分但是没拿到,因为题目里只说了是菊花,没说根节点是谁,判断的时候漏判了.

#include<bits/stdc++.h>
using namespace std;
int n,q;
int sons[200001],fa[200001],black[200001],isblack[200001];
int main(){
scanf("%d %d",&n,&q);
for(int i=2;i<=n;++i){
scanf("%d",&fa[i]);
sons[fa[i]]++;
}
for(int i=1;i<=q;++i){
int x;
scanf("%d",&x);
int ans=0;
for(int j=1;j<=x;++j){
scanf("%d",&black[j]);
isblack[black[j]]=true;
}
for(int j=1;j<=x;++j){
ans+=sons[black[j]];
}
for(int j=1;j<=x;++j){
if(!isblack[fa[black[j]]]) ans++;
else ans--;
}
printf("%d\n",ans);
for(int j=1;j<=x;++j){
isblack[black[j]]=false;
}
}
}

B.White and White

还是比较显然的 \(O(n^2 k)\) 暴力,对于此题显然可以设计 \(f_{i,j}\) 表示选到第 \(j\) 位,上次划分在 \(i\) 位置的最小值,可以得到:

\[f_{i,j}=\min_{1\le k\lt j} \{ f_{i-1,k}+(\sum^{x}_{k+1\le x\le j} a_{x})\mod p \}
\]

发现这个 \(\mod p\) 非常恶心,阻止我们进一步使用优先队列进行优化.

因此考虑从这个 \(\mod p\) 入手,因为我们每次进行取模都只是剪掉了一个 \(kp\ (k\in Z)\),即 \(f_{i,j}\equiv \sum^{x}_{1\le x\le j} a_{x}\pmod p\)

在我们进行状态转移时(即上式),对于两个 \(k=a,b\),将同余式传递,有

\[f_{i,j}\equiv f_{i-1,a}+\sum^{x}_{a\le x\le j}a_{x}\equiv f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\pmod p
\]

假设当 \(f_{i-1,a}+\sum^{x}_{a\le x\le j}a_{x}\lt f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\) 时,有 \(f_{i-1,a}\lt f_{i-1,b}\),此时移项有:

\[\sum^{x}_{a\le x\le j}a_{x}\lt f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}-f_{i-1,a}
\]

刚才我们知道

\[\sum^{x}_{a\le x\le j}a_{x}\equiv f_{i-1,a}-f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\pmod p
\]

这两个方程联立无解,因此假设不成立,我们得到:

\[f_{i-1,a}+\sum^{x}_{a\le x\le j}a_{x}\lt f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\rightarrow f_{i-1,a}\ge f_{i-1,b}
\]

通过此式判断最优决策点即可

此外本题卡空间,需要用滚动数组进行优化

#include<bits/stdc++.h>
using namespace std;
int n,k,p;
const int mod=1e9+7,inf=0x3f3f3f3f;
int sum[500001];
int f[500001][101];
int g[2][101]; //维护决策最小值
int main(){
cin>>n>>k>>p;
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;++i){
int x;cin>>x;
sum[i]=(sum[i-1]+x)%p;
f[0][0]=0;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=k;++j){
f[i][j]=f[g[i&1][j-1]][j-1]+((sum[i]-sum[g[i&1][j-1]])%p+p)%p;
}
for(int j=0;j<=k;++j){
if(f[i][j]<f[g[i&1][j]][j]) g[!(i&1)][j]=i;
else g[!(i&1)][j]=g[i&1][j];
}
}
cout<<f[n][k]<<endl;
}

UPD: 刚刚发现其实决策数组只需要开一维就行了

#include<bits/stdc++.h>
using namespace std;
int n,k,p;
const int mod=1e9+7,inf=0x3f3f3f3f;
int sum[500001];
int f[500001][101];
int g[101];
int main(){
cin>>n>>k>>p;
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;++i){
int x;cin>>x;
sum[i]=(sum[i-1]+x)%p;
f[0][0]=0;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=k;++j){
f[i][j]=f[g[j-1]][j-1]+((sum[i]-sum[g[j-1]])%p+p)%p;
}
for(int j=0;j<=k;++j){
if(f[i][j]<f[g[j]][j]) g[j]=i;
}
}
cout<<f[n][k]<<endl;
}

C.Black and Black

显然我们首先随便构造一组,只需要满足单调递增性质即可.

注意到,假如此时 \(ans\gt 0\),我们可以寻找一个位置 \(j\) 满足 \(\sum^{i}_{1\le i\le j}a_{i}=1\),这样的话,我们就可以对区间 \([1,j]\) 中的每个元素都减去 \(ans\),这样结果就会变为 \(ans-\sum^{i}_{1\le i\le j}(a_{i}\times ans)=0\),同时不会改变我们构造的单调递减的性质,或者,我们也可以选择一个位置 \(j\) 满足 \(\sum^{i}_{j\le i\le n}a_{i}=-1\),证明同理.

对于 \(ans\lt 0\),只需要选择 \(j\) 满足 \(\sum^{i}_{1\le i\le j}a_{i}=-1\) 或 \(\sum^{i}_{j\le i\le n}a_{i}=1\) 即可.

若不存在上述情况,即为无解.

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int a[200001],b[200001];
int foresum,backsum,fore,back;
void randcreate(){
for(int i=1;i<=n;++i){
b[i]=i;
}
}
int cal(){
int ans=0;
for(int i=1;i<=n;++i){
ans+=a[i]*b[i];
// cout<<a[i]<<" * "<<b[i]<<endl;
}
// cout<<ans<<endl;
return ans;
}
signed main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
randcreate();
int res=cal();
if(res==0){
cout<<"Yes"<<endl;
for(int i=1;i<=n;++i){
cout<<b[i]<<" ";
}
cout<<endl;
return 0;
}
else if(res>0){
for(fore=1;fore<=n;++fore){
foresum+=a[fore];
if(foresum>0) break;
}
for(back=n;back>=1;--back){
backsum+=a[back];
if(backsum<0) break;
}
// cout<<fore<<" : "<<back<<endl;
if(fore!=n+1) for(int i=1;i<=fore;++i) b[i]-=res;
else if(back!=0) for(int i=back;i<=n;++i) b[i]+=res;
else{
cout<<"No"<<endl;
return 0;
}
}
else{
for(fore=1;fore<=n;++fore){
foresum+=a[fore];
if(foresum<0) break;
}
for(back=n;back>=1;--back){
backsum+=a[back];
if(backsum>0) break;
}
// cout<<fore<<" : "<<back<<endl;
if(fore!=n+1) for(int i=1;i<=fore;++i) b[i]+=res;
else if(back!=0) for(int i=back;i<=n;++i) b[i]-=res;
else{
cout<<"No"<<endl;
return 0;
}
}
cout<<"Yes"<<endl;
for(int i=1;i<=n;++i){
cout<<b[i]<<" ";
}
cout<<endl;
}

D.Black and White

最暴力的做法是单次 \(n^{2}\) 枚举点对求距离最大值. \(O(qn^{2}\log n)\)

比较暴力的显然是直接单次两遍 DFS 求树的直径. \(O(qn^{2})\)

直接说正解,考虑到根据原树的 DFS 序将整棵树压缩成一个括号序列. \(O(q\log n)\)

注意到,自身封闭的括号序列一定是一颗完整的子树,因此,当我们需要求解 \(x,y\) 之间的距离时,应该考虑分别找到 \(x,y\) 的位置,然后提取出 \([pos_{x},pos_{y}]\) 之间的括号序列. 假如该序列中存在封闭的子括号序列,说明最短路径显然不会进入这颗子树(因为 \(x,y\) 均不在这个子树内,因此走进去是多余的),所以考虑消去路径上全部封闭的子括号序列,余下的括号个数即为路径长.

显然,这样的复杂度不够优秀,注意到区间括号序列是具有可合并性的,当我们合并两个括号序列的时候,根据上述所需,我们完全可以预先消去两个子区间内的全部封闭子括号序列,其次进行合并,再将新形成的括号序列继续与其他括号序列进行合并.

基于这样的思想,我们使用线段树来维护消除完毕的括号序列(即任意两点间距离,通过查询 \([l,r]\) 区间和即可获得 \(dis_{l,r}\)).

显然我们需要维护的有:DFS 序,各点在 DFS 序列上的位置,线段树(存储每个区间的左括号数目,右括号数目(特别地,由于维护跨区间距离的需要,我们还需要维护该树的子树的括号情况),以及该区间左右端点间距离(即剩余括号数)).

此外还需要判 \(-1\) 和 \(0\),记一个 \(cnt\) 即可.

#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int num,s[3000001],pos[1000001];
int n,m;
int cnt,tot;
bool c[100001];
vector<int>e[200001];
void dfs(int now,int fa){
s[++tot]=-1;
s[++tot]=now;
pos[now]=tot;
for(int i:e[now]){
if(i!=fa){
dfs(i,now);
}
}
s[++tot]=-2;
}
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
struct tree{
int a,b;
int l1,l2,r1,r2;
int dis;
}t[1200005];
void push(int id,int x){
t[id].a=t[id].b=0;
t[id].l1=t[id].l2=t[id].r1=t[id].r2=t[id].dis=-inf;
if(s[x]==-1){
t[id].b=1;
}
else if(s[x]==-2){
t[id].a=1;
}
else if(!c[s[x]]){
t[id].l1=t[id].r1=t[id].r2=t[id].l2=0;
}
}
void merge(int id){
if(t[tol].b>t[tor].a){
t[id].a=t[tol].a,t[id].b=t[tol].b-t[tor].a+t[tor].b;
}
else{
t[id].a=t[tol].a+t[tor].a-t[tol].b,t[id].b=t[tor].b;
}
t[id].l1=max({t[tol].l1,t[tor].l1+t[tol].a-t[tol].b,t[tor].l2+t[tol].a+t[tol].b});
t[id].l2=max(t[tol].l2,t[tor].l2-t[tol].a+t[tol].b);
t[id].r1=max({t[tor].r1,t[tol].r1-t[tor].a+t[tor].b,t[tol].r2+t[tor].a+t[tor].b});
t[id].r2=max(t[tor].r2,t[tol].r2+t[tor].a-t[tor].b);
t[id].dis=max({t[tol].r1+t[tor].l2,t[tol].r2+t[tor].l1,t[tol].dis,t[tor].dis});
}
void build(int id,int l,int r){
if(l==r){
push(id,l);
return;
}
int mid(l,r);
build(tol,l,mid);
build(tor,mid+1,r);
merge(id);
}
void modify(int id,int l,int r,int x){
if(l==r){
push(id,l);
return;
}
int mid(l,r);
if(x<=mid){
modify(tol,l,mid,x);
}
else{
modify(tor,mid+1,r,x);
}
merge(id);
}
int main(){
scanf("%d",&n);
cnt=n;
for(int i=1;i<=n-1;i++){
int x,y;
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
build(1,1,tot);
scanf("%d",&m);
for(int i=1;i<=m;i++){
char op='\n';int x;
while(op!='G' and op!='C') op=getchar();
if(op=='C'){
scanf("%d",&x);
if(c[x]){
c[x]=0;
cnt++;
}
else{
c[x]=1;
cnt--;
}
modify(1,1,tot,pos[x]);
}
else if(cnt==0){
printf("-1\n");
}
else if(cnt==1){
printf("0\n");
}
else{
printf("%d\n",t[1].dis);
}
}
}

暑假集训CSP提高模拟4的更多相关文章

  1. 2015UESTC 暑假集训总结

    day1: 考微观经济学去了…… day2: 一开始就看了看一道题目最短的B题,拍了半小时交了上去wa了 感觉自己一定是自己想错了,于是去拍大家都过的A题,十分钟拍完交上去就A了 然后B题写了一发暴力 ...

  2. 牛客网NOIP赛前集训营-提高组(第四场)游记

    牛客网NOIP赛前集训营-提高组(第四场)游记 动态点分治 题目大意: \(T(t\le10000)\)组询问,求\([l,r]\)中\(k(l,r,k<2^{63})\)的非负整数次幂的数的个 ...

  3. 牛客网NOIP赛前集训营-提高组(第四场)B区间

    牛客网NOIP赛前集训营-提高组(第四场)B区间 题目描述 给出一个序列$ a_1  \dots   a_n$. 定义一个区间 \([l,r]\) 是好的,当且仅当这个区间中存在一个 \(i\),使得 ...

  4. STL 入门 (17 暑假集训第一周)

    快速全排列的函数 头文件<algorithm> next_permutation(a,a+n) ---------------------------------------------- ...

  5. 牛客网NOIP赛前集训营-提高组(第四场)B题 区间

    牛客网NOIP赛前集训营-提高组(第四场) 题目描述 给出一个序列 a1, ..., an. 定义一个区间 [l,r] 是好的,当且仅当这个区间中存在一个 i,使得 ai 恰好等于 al, al+1, ...

  6. 牛客网NOIP赛前集训营-普及组(第二场)和 牛客网NOIP赛前集训营-提高组(第二场)解题报告

    目录 牛客网NOIP赛前集训营-普及组(第二场) A 你好诶加币 B 最后一次 C 选择颜色 D 合法括号序列 牛客网NOIP赛前集训营-提高组(第二场) A 方差 B 分糖果 C 集合划分 牛客网N ...

  7. 20190820 Tue 集训总结&NOIP模拟 27

    低谷度过了? 但是skyh阿卡了,还是反衬出我的辣鸡. T1知道要sort,却忘了判重,正解不如暴力分高,555. T2成功化出正解柿子,然后化过头了,化出了无法DP的柿子. 果然不够强,大神们一眼就 ...

  8. 暑假集训Day2 互不侵犯(状压dp)

    这又是个状压dp (大型自闭现场) 题目大意: 在N*N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子. ...

  9. 暑假集训Day1 整数划分

    题目大意: 如何把一个正整数N(N长度<20)划分为M(M>=1)个部分,使这M个部分的乘积最大.N.M从键盘输入,输出最大值及一种划分方式. 输入格式: 第一行一个正整数T(T<= ...

  10. #10471. 「2020-10-02 提高模拟赛」灌溉 (water)

    题面:#10471. 「2020-10-02 提高模拟赛」灌溉 (water) 假设只有一组询问,我们可以用二分求解:二分最大距离是多少,然后找到深度最大的结点,并且把它的\(k\)倍祖先的一整子树删 ...

随机推荐

  1. CF301B Yaroslav and Time 题解

    CF301B 这不最短路的板子题吗? 思路 用 \(ak\) 代表走到第 \(k\) 点时的可恢复单位时间的值. \(i\) 到 \(j\) 的距离是 \(\left ( \left | xi-xj ...

  2. 深度学习 玩游戏 Q-LEARNING

    游戏里面非玩家的角色行为,即 AI. 腾讯的 Ai 游戏框架:TencentOpen. 介绍: Agent,behavior tree, 大概意思就是 通过自己的框架来确定 ai 行为,然后通过 ag ...

  3. 图灵课堂netty 仿微信开发

    通信的图文示例 以下是需要实现的前端界面, 准备工作:开始实现前需要技术关健字解释 第一步,这儿直接建一个maven 项目 就好,只要是可能用maven 管理包的环境就行,课程使用的版本是 java ...

  4. java中的Context

    在java编程中,上下文(Context)是指程序运行时的环境和状态的集合.包括了类对象变量方法等运行时的相关数据 在类中,我们可以通过this获取当前类的变量.方法的上下文, 例如getset方法: ...

  5. 分享一个idea的文档汉化插件

    展示效果: 如何获取:

  6. MSPM0G3507外设DMA学习笔记

    概述 变量的存储 正常情况下,变量存储在SRAM中,如果要发送该变量的值到外设,需要调用内核操作,使SRAM中的数据送到外设. 此类型操作过多会导致占用CPU高,整体卡顿. DMA控制概述 DMA:D ...

  7. Python和RPA网页自动化-异常处理Try方法

    我们在跑自动化时为了捕获和处理异常,会增加异常处理Try方法.下面来看看Python和RPA网页自动化中异常处理Try的用法 1.Python中异常处理try的用法 try: test = " ...

  8. 实现一个终端文本编辑器来学习golang语言:第二章Raw模式下的输入输出

    从第二章开始,在每个小节的最后都会有一些代码实操作业,你可以选择自己完成(比较推荐),再对照我的实现方式,当然也可以直接看我的代码实现.不过,之后的各个功能实现,我都会基于我先前的代码实现版本,在它的 ...

  9. Regardless of the outcome of the Russia-Ukraine war, how can Ukraine avoid paying the weapon fees to the United States after the war?

    According to the agreement between the Ukrainian government and the United States, regardless of the ...

  10. mojo编程语言:编译后的mojo二进制执行文件调用python库报错——设置MOJO_PYTHON_LIBRARY变量

    代码: from python import Python fn f() raises: # This is equivalent to Python's `import numpy as np` l ...