暑假集训CSP提高模拟4
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\) 位置的最小值,可以得到:
\]
发现这个 \(\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-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}\),此时移项有:
\]
刚才我们知道
\]
这两个方程联立无解,因此假设不成立,我们得到:
\]
通过此式判断最优决策点即可
此外本题卡空间,需要用滚动数组进行优化
#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的更多相关文章
- 2015UESTC 暑假集训总结
day1: 考微观经济学去了…… day2: 一开始就看了看一道题目最短的B题,拍了半小时交了上去wa了 感觉自己一定是自己想错了,于是去拍大家都过的A题,十分钟拍完交上去就A了 然后B题写了一发暴力 ...
- 牛客网NOIP赛前集训营-提高组(第四场)游记
牛客网NOIP赛前集训营-提高组(第四场)游记 动态点分治 题目大意: \(T(t\le10000)\)组询问,求\([l,r]\)中\(k(l,r,k<2^{63})\)的非负整数次幂的数的个 ...
- 牛客网NOIP赛前集训营-提高组(第四场)B区间
牛客网NOIP赛前集训营-提高组(第四场)B区间 题目描述 给出一个序列$ a_1 \dots a_n$. 定义一个区间 \([l,r]\) 是好的,当且仅当这个区间中存在一个 \(i\),使得 ...
- STL 入门 (17 暑假集训第一周)
快速全排列的函数 头文件<algorithm> next_permutation(a,a+n) ---------------------------------------------- ...
- 牛客网NOIP赛前集训营-提高组(第四场)B题 区间
牛客网NOIP赛前集训营-提高组(第四场) 题目描述 给出一个序列 a1, ..., an. 定义一个区间 [l,r] 是好的,当且仅当这个区间中存在一个 i,使得 ai 恰好等于 al, al+1, ...
- 牛客网NOIP赛前集训营-普及组(第二场)和 牛客网NOIP赛前集训营-提高组(第二场)解题报告
目录 牛客网NOIP赛前集训营-普及组(第二场) A 你好诶加币 B 最后一次 C 选择颜色 D 合法括号序列 牛客网NOIP赛前集训营-提高组(第二场) A 方差 B 分糖果 C 集合划分 牛客网N ...
- 20190820 Tue 集训总结&NOIP模拟 27
低谷度过了? 但是skyh阿卡了,还是反衬出我的辣鸡. T1知道要sort,却忘了判重,正解不如暴力分高,555. T2成功化出正解柿子,然后化过头了,化出了无法DP的柿子. 果然不够强,大神们一眼就 ...
- 暑假集训Day2 互不侵犯(状压dp)
这又是个状压dp (大型自闭现场) 题目大意: 在N*N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子. ...
- 暑假集训Day1 整数划分
题目大意: 如何把一个正整数N(N长度<20)划分为M(M>=1)个部分,使这M个部分的乘积最大.N.M从键盘输入,输出最大值及一种划分方式. 输入格式: 第一行一个正整数T(T<= ...
- #10471. 「2020-10-02 提高模拟赛」灌溉 (water)
题面:#10471. 「2020-10-02 提高模拟赛」灌溉 (water) 假设只有一组询问,我们可以用二分求解:二分最大距离是多少,然后找到深度最大的结点,并且把它的\(k\)倍祖先的一整子树删 ...
随机推荐
- C#全局键盘监听(Hook)的使用
一.为什么需要全局键盘监听? 在某些情况下应用程序需要实现快捷键执行特定功能,例如大家熟知的QQ截图功能Ctrl+Alt+A快捷键,只要QQ程序在运行(无论是拥有焦点还是处于后台运行状态),都可以按下 ...
- Python 按分类样本数占比生成并随机获取样本数据
按分类样本数占比生成并随机获取样本数据 By:授客 QQ:1033553122 开发环境 win 10 python 3.6.5 需求 已知样本分类,每种分类的样本占比数,及样本总数,需要随机获取这些 ...
- Vue 根据鼠标悬停目标元素上方显示、隐藏指定元素交互实现
Vue 根据鼠标悬停目标元素上方显示.隐藏指定元素交互实现 By:授客 QQ:1033553122 开发环境 win10 element-ui "2.13.1" vue &qu ...
- JavaScript中的new map()和new set()使用详细(new map()和new set()的区别)
简介:new Map(): 在JavaScript中,new Map()用于创建一个新的 Map 对象.Map 对象是一种键值对的集合,其中的键是唯一的,值可以重复.new Set(): 在JavaS ...
- 利用Elasticsearch实现地理位置、城市搜索服务
最近用到一些简单的地理位置查询接口,基于当前定位获取用户所在位置信息(省市区),然后基于该信息查询当前区域的......提供服务. 然后就自己研究了下GIS,作为一个程序员.自己能不能实现这个功能呢? ...
- os.popen(cmd) 与 os.system(cmd) 的区别
os.popen(cmd) 与 os.system(cmd) 的区别 1,os.popen(cmd) 不会直接返回任何数据,os.system(cmd) 会直接输出结果(返回的却是int状态码) 2, ...
- 【JavaScript】下滑线命名转驼峰命名处理
同事写接口返回的JSON属性名称始终不一致,一会下划线一会驼峰 然后自己封装了一个: function toHump(name){ var newName = name.toLowerCase(); ...
- 【转载】 TensorFlow - 框架实现中的三种 Graph图结构
原文地址: https://zhuanlan.zhihu.com/p/31308381 -------------------------------------------------------- ...
- Ubuntu18.04 系统环境下 vscode中忽略pylint某些错误或警告
相关: ubuntu18.04系统环境下使用vs code安装pylint检查python的代码错误 ====================================== 假设已经在前文(ht ...
- 2.3.6版本发布!Apache SeaTunnel Zeta引擎迎来新架构!
Apache SeaTunnel 2.3.6 版本于近日正式发布,社区期待的 SeaTunnel Zeta Master/Worker 新架构.事件通知机制.支持动态编译的transform等新功能和 ...