GDOI2018 Day1 题目总结
T1:农场
题意:有一个长为 $n$ 的序列 $a$,要求将其分成尽可能多的部分,使得每一部分的 $a_i$ 的和相等。求最多能分成的部分数。
$30\%:1\le n\le 1000$
$80\%:1\le n\le 10^5$
$100\%:1\le a_i\le 10,1\le \sum a_i\le 10^6$
这题不难,说一下我在考场的思路:
首先答案应该是 $\sum a_i$ 的约数。那么可以转化一下,变成找到满足要求的最小的和(也是其约数)
进一步想到前缀和。我们发现 $x$ 满足条件,当且仅当 $x,2x,3x\dots$ 全部在前缀和中出现。
于是考场上写了个80分的暴力 $O(n\sqrt{n})$(枚举约数 $O(\sqrt{n})$,判断 $O(n)$)
后来发现可以做到更快:因为总和不超过 $10^6$,因此可以开桶。复杂度 $O(\sigma(n))<O(n\log n)$。
但是 $O(n\sqrt{n})$ 可以过?
#include<bits/stdc++.h>
using namespace std;
int n,sum;
int fac[],fl;
bool vis[];
void split(int x){ //求出总和的所有约数
for(int i=;i*i<=x;i++)
if(x%i==){
fac[++fl]=i;
if(i*i!=x) fac[++fl]=x/i;
}
sort(fac+,fac+fl+);
}
int main(){
scanf("%d",&n);
for(int i=;i<=n;i++){
int a;
scanf("%d",&a);
sum+=a;
vis[sum]=true; //对前缀和开桶
}
split(sum);
for(int i=;i<=fl;i++){
bool flag=true;
for(int j=fac[i];j<=sum;j+=fac[i]) //判断是否满足
if(!vis[j]){ //不满足
flag=false;break;
}
if(flag){
printf("%d\n",sum/fac[i]);return ; //答案为总和/单个和=段数
}
}
}
T2:密码锁
题意:有一个长为 $n$ 的序列 $a$,每一次操作可以让区间 $[l,r]$ 间所有 $a_i$ 加一或所有 $a_i$ 减一 $\pmod m$。问最少多少次操作可以让序列变为全 $0$。
测试点 1~4:$1\le n\le 4,2\le m\le 10$
测试点 5~9: $1\le n\le 10^5,2\le m\le 3$
测试点 10~15:$2\le n,m\le 3000$
测试点 16~18:$2\le n\le 2\times 10^5$
全部20个测试点:$1\le n\le 10^6,2\le m\le 10^9$
这题很有思维难度。像我这种考场上连20分BFS都没想到,写了个 $\sum\min(a_i,m-a_i)$ 的……居然还有5分
看到区间加减操作,想到差分(设为 $d,d_i=(a_i-a_{i-1})\operatorname{mod}m(i\le n),d_{n+1}=m-a_n$)。
把 $a_{i-1}$ 进行 $d_i$ 次加操作后可以变成 $a_i$,或者进行 $m-d_i$ 次减操作后可以变成 $a_i$。
所以想到一个贪心:优先对 $d_i$ 小的进行加操作,剩下的进行减操作。
而对一个区间 $[l,r]$ 进行 $x$ 次操作实际上就是 $d_l-=x,d_{r+1}+=x$,整个序列变为 $0$ 就是 $d$ 变为全 $0$。
又因为每次操作都是一加一减,所以对 $d$ 进行的加操作次数等于减操作次数。
那么问题的本质就是把 $d_i$ 进行分组,使得第一组的 $\sum d_i$ 等于第二组的 $\sum m-d_i$。此时答案也就是这个相等的和。
结合刚刚那个贪心的想法,只需要把 $d_i$ 排序,枚举断点 $p$,分成 $i\le p$ 和 $i>p$ 两组。当这两组的和相等时这个和就是答案。
直接枚举断点再求和还不够,加上前缀和和后缀和就可以了。复杂度 $O(n\log n)$。
注:$d_{n+1}=m-a_n$ 一定也要考虑,否则会导致 $[l,n]$ 的操作没有意义。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,a[],dif[]; //dif是差分
ll sum,pre[]; //pre是差分排序后的前缀和
int main(){
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++){
scanf("%d",a+i);
dif[i]=(a[i]-a[i-]+m)%m;
}
dif[n+]=(m-a[n])%m; //注意,这个不能漏
sort(dif+,dif+n+);
for(int i=;i<=n+;i++) pre[i]=pre[i-]+dif[i]; //做前缀和
for(int i=n+;i>=;i--){
sum+=m-dif[i]; //做后缀和
if(sum==pre[i-]){ //前缀和和后缀和相等,这就是答案
printf("%lld\n",sum);return ;
}
}
}
T3:涛涛摘苹果
题意:有一棵 $n$ 个节点的苹果树,根节点为 $1$,初始每个点都有一个重为 $a_i$ 的苹果。每天早上根节点的苹果会消失。每天下午所有苹果都会下落一层(也就是下落到它的父亲节点上)。某些天(第 $t$ 天)的晚上编号 $x$ 的点上会多长出一个重为 $w$ 的苹果,这样的操作共有 $m$ 个。某些天(第 $t$ 天)早晨根节点苹果消失前会询问以 $x$ 为根的子树中所有苹果的重量之和,共有 $q$ 个询问。
$10\%:$ 所有输入数据都不超过 $5000$
另 $10\%:$ 树退化成一条链,其中 $i$ 与 $i-1$ 有边
另 $20\%:$ 所有询问满足 $x=1$
另 $20\%:m=0$
$100\%:$ 所有输入数据都不超过 $10^5$
先考虑 $m=0$ 怎么做:
我们发现苹果 $x$ 对第 $t$ 天在节点 $y$ 的询问产生贡献,当且仅当:($sz[x]$ 表示 $x$ 的子树大小,$dfn[x]$ 表示 $x$ 的dfs序,$dep[x]$ 表示 $x$ 的深度)
$dep[x]\ge dep[y]+t-1$
$dfn[y]\le dfn[x]\le dfn[y]+sz[y]-1$
那么就可以乱搞了。(既然不是正解,不说做法了,其实是我不会)
$m\ne 0$ 时,根据条件 $1$,我们发现添加的苹果 $x$ 相较其它苹果少下落了 $t$ 天,可以想象从 $x$ 新连出一个节点 $y$,使得 $dep[y]=dep[x]+t$,也就是从 $x$ 连出一条权 $t$ 的边。
根据条件 $2$,他应该在子树内,连出一条边恰好符合要求。
但这样不够,还有一个时间限制!只有添加操作的 $t\le$ 询问操作的 $t-1$,才能产生贡献。
现在是个三维偏序,CDQ即可。
时间复杂度:$O((n+m+q)\log^2(n+m+q))$
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
inline int read(){
char ch=getchar();int x=,f=;
while(ch<'' || ch>'') f|=ch=='-',ch=getchar();
while(ch>='' && ch<='') x=x*+ch-'',ch=getchar();
return f?-x:x;
}
int n,m,q,cnt;
int el,head[maxn*],to[maxn*],w[maxn*],nxt[maxn*];
int dep[maxn*],sz[maxn*],dfn[maxn*];
ll ans[maxn],bit[maxn*];
struct oper{ //tp=0表示修改,tp=1表示询问,tim表示时间,x表示节点,w表示权值(可能没有),id表示原编号
int tp,tim,x,w,id;
}op[maxn*];
inline void add(int u,int v,int w_){
to[++el]=v;w[el]=w_;nxt[el]=head[u];head[u]=el;
}
inline bool cmptim(const oper &o1,const oper &o2){ //注意只有修改会对询问产生贡献,所以时间一样的要把修改放前面
if(o1.tim!=o2.tim) return o1.tim<o2.tim;
return o1.tp<o2.tp;
}
inline bool cmpdep(const oper &o1,const oper &o2){
return dep[o1.x]>dep[o2.x];
}
inline bool cmphhh(const oper &o1,const oper &o2){ //各种排序
return dep[o1.x]+o1.tim>dep[o2.x]+o2.tim;
}
inline void modify(int p,int v){
for(;p<=n+m;p+=p&-p) bit[p]+=v;
}
inline ll query(int p){
ll ans=;for(;p;p-=p&-p) ans+=bit[p];return ans;
}
inline ll query(int l,int r){
return query(r)-query(l-);
} //以上树状数组
void dfs(int u,int f){ //预处理dep,dfn,sz
sz[u]=;dfn[u]=++cnt;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
dep[v]=dep[u]+w[i];
dfs(v,u);sz[u]+=sz[v];
}
}
void CDQ(int l,int r){ //CDQ分治
if(l==r) return;
int mid=(l+r)>>;
CDQ(l,mid);CDQ(mid+,r);
sort(op+l,op+mid+,cmpdep); //左边按照dep排序
sort(op+mid+,op+r+,cmphhh); //右边按照dep+time排序
int cl=l;
FOR(i,mid+,r){
for(;cl<=mid && dep[op[cl].x]>=dep[op[i].x]+op[i].tim-;cl++) //cl能对i产生贡献
if(op[cl].tp==) modify(dfn[op[cl].x],op[cl].w); //是修改,插入树状数组
if(op[i].tp==) ans[op[i].id]+=query(dfn[op[i].x],dfn[op[i].x]+sz[op[i].x]-); //是询问,添加贡献
}
FOR(i,l,cl-) if(op[i].tp==) modify(dfn[op[i].x],-op[i].w); //清空树状数组
}
int main(){
n=read();m=read();q=read();
FOR(i,,n) op[i]=(oper){,,i,read(),}; //一开始的苹果也可以看成是添加操作
FOR(i,,n-){
int u=read(),v=read();
add(u,v,);add(v,u,); //边权为1
}
FOR(i,,m){
int t=read(),x=read(),w=read();
add(x,i+n,t); //连虚边
op[i+n]=(oper){,t+,i+n,w,}; //添加操作(把时间设为t+1会更方便排序)
}
FOR(i,,q){
int t=read(),x=read();
op[i+n+m]=(oper){,t,x,,i}; //询问操作
}
dfs(,);
sort(op+,op+n+m+q+,cmptim); //按时间排序
CDQ(,n+m+q);
FOR(i,,q) printf("%lld\n",ans[i]);
}
T4:小学生图论题
题意:给出一个 $n$ 个点的有向竞赛图。有 $m$ 条链,第 $i$ 条长 $k_i$,依次经过 $a_1,a_2,\dots,a_{k_i}$。这些链没有公共点。链上所有边的方向已知,为链经过的方向,其他边方向等概率随机。求这个图强联通分量的期望个数对 $998244353$ 取模。
测试点 1~2:$1\le n\le 1000,m=0$
测试点 3~4:$1\le n,m\le 1000$
测试点 5:$k_i=2$
全部10个测试点:$1\le n,m\le 10^5,2\le k_i\le n,\sum k_i\le n$
不会做。
有朝一日会做了再来填坑吧……
GDOI2018 Day1 题目总结的更多相关文章
- [gdoi2018 day1]小学生图论题【分治NTT】
正题 题目大意 一张随机的\(n\)个点的竞赛图,给出它的\(m\)条相互无交简单路径,求这张竞赛图的期望强联通分量个数. \(1\leq n,m\leq 10^5\) 解题思路 先考虑\(m=0\) ...
- contesthunter CH Round #64 - MFOI杯水题欢乐赛day1 solve
http://www.contesthunter.org/contest/CH Round %2364 - MFOI杯水题欢乐赛 day1/Solve Solve CH Round #64 - MFO ...
- 【GDOI】2018题目及题解(未写完)
我的游记:https://www.cnblogs.com/huangzihaoal/p/11154228.html DAY1 题目 T1 农场 [题目描述] [输入] 第一行,一个整数n. 第二行,n ...
- [51nod1357]密码锁
有一个密码锁,其有N位,每一位可以是一个0~9的数字,开启密码锁需要将锁上每一位数字转到解锁密码一致.这个类似你旅行用的行李箱上的密码锁,密码锁的每一位其实是一个圆形转盘,上面依次标了0,1,...9 ...
- [题解]玩具谜题(toy)
玩具谜题(toy) 来源:noip2016 提高组 day1 [题目描述] 小南有一套可爱的玩具小人, 它们各有不同的职业. 有一天, 这些玩具小人把小南的眼镜藏了起来. 小南发现玩具小人们围成了一个 ...
- [51nod1357]密码锁 暨 GDOI2018d1t2
有一个密码锁,其有N位,每一位可以是一个0~9的数字,开启密码锁需要将锁上每一位数字转到解锁密码一致.这个类似你旅行用的行李箱上的密码锁,密码锁的每一位其实是一个圆形转盘,上面依次标了0,1,...9 ...
- 【心情】HNOI2018游记
Day 0. 全机房的人好像都在做题.然而下午是社团节的游园会,身为社干的我风风雨雨在外面各种搬凳子搬椅子换场地招待外校同学……就这样我好像什么都没有复习. 晚上就一起去酒店了.大概因为是高一的缘故, ...
- HNOI2019爆零记
HNOI2019真-爆零祭 我怎么这么菜QAQ day-37 从学科溜过来搞OI. 班主任一直在谈论我退役的事情,这就是NOIP挂分的后果...说我没考好就找理由,人家xxxxxxx可不是xxxxxx ...
- 2019.7 佳木斯培训A层
day1题目及题解 day2题目及题解 day3题目及题解 day4题目及题解 day5题目及题解
随机推荐
- 20155223 Exp7 网络欺诈防范
20155223 Exp7 网络欺诈防范 基础问题回答 通常在什么场景下容易受到DNS spoof攻击? 无设防或防护力特别弟弟低的公共局域网,或者是在同一个局域网下. 在日常生活工作中如何防范以上两 ...
- 微信小程序之 动画 —— 自定义底部弹出层
wxml: <view class='buy' bindtap='showBuyModal'>立即购买</view> <!-- 点击立即购买 弹出购买遮罩层 --> ...
- kubernetes(k8s) 的常用命令
1.查询副本[root@master ~]# kubectl get pods2.删除一个副本[root@master ~]# kubectl get pods 3.启动一个容器副本[root@mas ...
- 整理一些常用的前端CND加速库,VUE,Jquery,axios
VUE https://cdn.staticfile.org/vue/2.2.2/vue.min.js Jquery https://cdn.bootcss.com/jquery/3.4.0/jque ...
- [SHELL]输入输出重定向与管道
一 . 输出重定向(将命令的输出重定向到文件): ls -al > test 以覆盖的方式写入 ls -al >> test 以追加的方式写入 二 . 输入重定向(将文件的内容重定向 ...
- 转载别人的一篇关于git的文章
转载网址:https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
- linux一切皆文件之tcp socket描述符(三)
一.知识准备 1.在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,pipe队列) 2.操作这些不同的类型就像操作文件一样,比如增删改查等 二.环境准备 ...
- C++基础知识(3)
C++内置的数据类型:基本类型.复合类型 基本类型:整型,浮点型,字符型 复合类型:数组,字符串,指针和结构 复合数据类型是在基本数据类型的基础上创建的 要知道系统中整数的最大长度,可以在程序中使用C ...
- Intellij Idea 创建JavaWeb项目入门(一)
Idea创建JavaWeb项目步骤:1.打开Intellij Idea IDE,然后点击Create New Project 2.左侧选择Java Enterprise,右侧选择Web Applica ...
- hexo搭建
http://blog.csdn.net/gdutxiaoxu/article/details/53576018