HGOI 20190303 题解
/*
记一串数字真难。 5435
今天比赛又是hjcAK的一天。
今天开题顺序是312,在搞T1之前搞了T3
昨天某谷月赛真是毒瘤。
但是讲评的同学不错,起码T4看懂了...
构造最优状态然后DP的思路真妙
*/
Problem A lcp
给出字符串S,m个询问,每个询问含有$l1,r1,l2,r2$求|S|子串$[l1,r1]$和$[l2,r2]$的LCP(最长公共前缀)
对于100%的数据$ 1 \leq |S|,m \leq 10^5 , l1 \leq r1 ,l2 \leq r2$
考虑二分答案套字符串Hash,于是就不用KMP了(我不会KMP)
再说一下Hash的思路吧hash[i]表示S前i个字符的前缀哈希值,设基底为E=$31$,模数mo=$10^9+9$
令hash[0]=0;对于$i \geq 1 计算方法如下 :hash[i]=hash[i-1] \times E+Val(s[i]) $ Val(x)是一个映射把char类型的x映射成一个int类型
所以利用前缀和的思想,如果不计模造成的负数问题$ Hash(l,r)=hash[r]-hash[l-1] \times E^{r-l+1} $
如果考虑模数造成负数问题那么要多mo几次,即 Hash(l,r) = ( (hash[r] - (hash[l-1] * pow[r-l+1] % mo) ) % mo + mo) % mo
得函数Hash(l,r)表示串S的子串[l,r]的哈希值。
那么这样就可以$O(1)$判断两个子串是不是相等了。套个二分就过了。
复杂度$O(m log_2 n)$
# include <bits/stdc++.h>
# define int long long
# define hash HASH
# define pow Pow
using namespace std;
const int N=1e5+;
const int mo=1e9+;
const int E=;
char s[N];
int n,m,hash[N],pow[N];
inline int read()
{
int X=,w=; char c=;
while(c<''||c>'') {w|=c=='-';c=getchar();}
while(c>=''&&c<='') X=(X<<)+(X<<)+(c^),c=getchar();
return w?-X:X;
}
int val(char ch){return ch-'a';}
int Hash(int l,int r){
return ((hash[r]-hash[l-]*pow[r-l+]%mo)%mo+mo)%mo;
}
bool check(int len,int l1,int l2){
if ((int) Hash(l1,l1+len-)==(int) Hash(l2,l2+len-)) return ;
else return ;
}
signed main()
{
freopen("lcp.in","r",stdin);
freopen("lcp.out","w",stdout);
n=read();m=read();
scanf("%s",s+);
pow[]=;
for (int i=;i<=n;i++) pow[i]=pow[i-]*E%mo;
for (int i=;i<=n;i++)
hash[i]=(hash[i-]*E+val(s[i]))%mo;
int l1,r1,l2,r2;
while (m--) {
l1=read();r1=read();l2=read();r2=read();
int l=,r=min(r1-l1+,r2-l2+),ans=;
while (l<=r) {
int mid=(l+r)>>;
if (check(mid,l1,l2)) ans=mid,l=mid+;
else r=mid-;
}
printf("%lld\n",ans);
}
return ;
}
lcp.cpp
Problem B Save
现在有n个粮仓,从高到低且间距是1被排在一个山坡上。对于一个粮仓可以进行扑灭和转移操作。
其中扑灭操作的代价是 b[i] , 转移操作的代价是a[i] ( 转移到最近的一个高度较低的已经执行过扑灭操作的粮仓且满足距离差$\Delta d \leq $d[i] )
对于 n 个粮仓执行完毕两个操作中的一个,求最小代价。
对于100%的数据$n \leq 10^5 , a[i],b[i] \leq 100 $
首先考虑一个问题第n个粮仓是一定是需要扑灭的。
那么f[i]就表示为前i个粮仓不被烧毁时候的最小代价,那么显然第i个粮仓的操作是不能为转运。
那么f[i]必然满足第i个粮仓是扑灭操作的。
我们考虑f[i]从f[j]转移过来,其中j应该是一个可以被转移过来的值,即$max(0,i - d[i] - 1) \leq j < i $
那么对于[j+1,i)这么多个粮仓,每一个粮仓只能从上到下转移到第i个粮仓 , 在所有转移中的最小值就是 f[i] 的最优值。
即$f_i = \min\limits_{max(0,i-d_i-1)\leq j<i} \{ f_j + \sum\limits_{k=j+1}^{i-1} a_k + b_j\}$
但是如果是任意一个的话,那么在[j+1,i)这段中可以选择扑灭,也可以选择原地建站,
而由于每一个物品运费是一样的,那么在这个区间中无论运到中间某一个站点,或者直接运到i点对答案贡献影响是一样的。
我们就可以考虑这个贪心,也就是如果这个点运费比扑灭代价更小那么就直接运到i,否则原地建站。
这个时候dp方程就是 $f_i = \min\limits_{max(0,i-d_i-1)\leq j<i} \{ f_j + \sum\limits_{k=j+1}^{i-1} \min \{ a_k , b_k\} + b_j\}$
显然可以前缀和优化到$O(n^2)$
即 $ 令 s_i = \sum\limits _{j=1}^i a_j 或 s_i = \sum\limits _{j=1}^i \min \{ a_j , b_j \} $
转移改写为 $f_i= \min \limits_{max(0,i-d_i-1)\leq j <i} \{ f_j-s_j \} +b_i +s_{i-1} $
观察这个dp式子发现对于一个确定的j,$f_j-s_j$ 是一个定值,而后面和j没有关系只和i有关系,可看做常量,
可以使用数据结构线段树 (或者二分法优化) , 即记录 区间max值,注意下标不能为0所以所有在线段树中下标+1;
这里介绍一种不使用线段树的方法(即二分法优化),单调栈+二分查找,也可以完成类似的操作,
头部不弹出,因为之前用到的虽然不在当前范围内,但是后续可能用到头部被弹出的点,造成答案偏大
尾部可以弹出,保证栈单调递增,因为当前进来的元素比尾部的离下个决策更近且更优,所以前面那个决策就不用了。正确性显然。
然后每一个元素进队1次,出队1次,然后查找最大复杂度是$ O(log_2 n) $ ,整个算法复杂度为$O(n log_2 n)$
标程给的是“最近的”,我被坑了!!!
复杂度$O(n log_2 n)$
# include <bits/stdc++.h>
# define fp(i,s,t) for (int i=s;i<=t;i++)
# define inf (0x3f3f3f3f)
using namespace std;
const int N=1e5+;
int a[N],b[N],d[N],s[N],n,f[N],c[N<<];
inline int read()
{
int X=,w=; char c=;
while(c<''||c>'') {w|=c=='-';c=getchar();}
while(c>=''&&c<='') X=(X<<)+(X<<)+(c^),c=getchar();
return w?-X:X;
}
void update(int x,int l,int r,int pos,int opx)
{
if (l==r) {c[x]=opx;return;}
int mid=(l+r)/;
if (pos<=mid) update(*x,l,mid,pos,opx);
else update(*x+,mid+,r,pos,opx);
c[x]=min(c[*x],c[*x+]);
}
int query(int x,int l,int r,int opl,int opr)
{
if (opl<=l&&r<=opr) return c[x];
int mid=(l+r)/;
int ret=inf;
if (opl<=mid) ret=min(ret,query(*x,l,mid,opl,opr));
if (opr>mid) ret=min(ret,query(*x+,mid+,r,opl,opr));
return ret;
}
int main()
{
freopen("save.in","r",stdin);
freopen("save.out","w",stdout);
n=read(); s[]=;
fp(i,,n) a[i]=read();
fp(i,,n) b[i]=read();
fp(i,,n) d[i]=read(),s[i]=s[i-]+a[i];
memset(c,0x3f,sizeof(c));
memset(f,0x3f,sizeof(f)); f[]=;
update(,,n,,);
for (int i=;i<=n;i++) {
f[i]=query(,,n,max(,i-d[i]-)+,i-+)+b[i]+s[i-];
update(,,n,i+,f[i]-s[i]);
}
printf("%d\n",f[n]);
return ;
}
save.cpp
Problem C Seven
树上路径时7的倍数的有几条?
对于100%的数据 $n \leq 10^5 $
直接淀粉质就行,参考https://www.cnblogs.com/ljc20020730/p/10347198.html
这里贴一下代码,和P2634 [国家集训队]聪聪可可 是一个题目。
复杂度是$O(n log_2 n)$
# include <bits/stdc++.h>
# define int long long
# define LL long long
using namespace std;
const int N=2e5+;
struct rec{ int pre,to,w; }a[N<<];
int n,tot,ans,SIZE,root;
int head[N],d[N],size[N],f[N];
int r[];
bool use[N];
int max(int x,int y){return (x>y)?x:y;}
int min(int x,int y){return (x>y)?y:x;}
inline int read()
{
int X=,w=; char c=;
while(c<''||c>'') {w|=c=='-';c=getchar();}
while(c>=''&&c<='') X=(X<<)+(X<<)+(c^),c=getchar();
return w?-X:X;
}
void adde(int u,int v,int w)
{
a[++tot].pre=head[u];
a[tot].to=v;
a[tot].w=w;
head[u]=tot;
}
void Get_Root(int u,int fath)
{
f[u]=,size[u]=;
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to;
if (v==fath||use[v]) continue;
Get_Root(v,u);
f[u]=max(f[u],size[v]);
size[u]+=size[v];
}
f[u]=max(f[u],SIZE-size[u]);
if (f[u]<f[root]) root=u;
}
int cnt;
void Get_Dist(int u,int fath,int L)
{
r[L%]++;
d[++cnt]=L; size[u]=;
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to;
if (v==fath||use[v]) continue;
Get_Dist(v,u,L+a[i].w);
size[u]+=size[v];
}
}
int Get_Ans(int u,int fath,int L)
{
cnt=; int ret=;
memset(r,,sizeof(r));
Get_Dist(u,fath,L);
for (int i=;i<=cnt;i++)
if (d[i]%==) ret+=r[]-;
else if (d[i]%==) ret+=r[];
else if (d[i]%==) ret+=r[];
else if (d[i]%==) ret+=r[];
else if (d[i]%==) ret+=r[];
else if (d[i]%==) ret+=r[];
else if (d[i]%==) ret+=r[];
return ret;
}
void solve(int u)
{
use[u]=true;
ans+=(LL)Get_Ans(u,,);
for (int i=head[u];i;i=a[i].pre){
int v=a[i].to; if (use[v]) continue;
ans-=(LL)Get_Ans(v,u,a[i].w);
SIZE=size[v],root=;
Get_Root(v,u);
solve(root);
}
}
signed main()
{
freopen("seven.in","r",stdin);
freopen("seven.out","w",stdout);
f[]=1e9; n=read();
int u,v,w;
for (int i=;i<n;i++) {
u=read();v=read();w=read();
adde(u,v,w); adde(v,u,w);
}
SIZE=n; root=;
Get_Root(,);
solve(root);
printf("%lld\n",ans>>);
return ;
}
seven(divide).cpp
我们应该学会一题多解。所以这道题有O(n)的树形dp可以解决。
有些树形dp是不会有重复的,比如这道题目。
我们可以设f[u][i] 其中$i \in [0,6] $表示 u下面的子树 的节点有多少个v和u距离d%7=i的。
我们f[u][i]可以从儿子f[v][i] 那里转移而来
首先设c[i] ($ i\in [0,6] $)数组表示u下面的某个确定儿子$ u_{son} $子树节点有多少个v和$ u_{lson}$ 距离为 $ d_{son} \% 7 =i $
这个c数组可以从$ f[v][i] + w_{now} $ 转移过来,即 $ c[(i+w) \% 7]=f[v][i] $
然后统计答案,也就是说当前c数组知识表示f[u][i]的一部分,而且这一部分是没有被加到f[u][i]中的,那么
已经被加入f[u][i]的答案 到 当前子树的每一个点 可以存在路径 符合条件 从这里 求答案 即 $ ans += f[u][i] \times c[ (7-i) \% 7 ],i\in [0,6] $
然后把 c数组统计到f[u][i]中 $ f[u][i]+=c[i] , i \in [0,6] $
复杂度$ O(n) $
# include <bits/stdc++.h>
using namespace std;
# define int long long
const int N=1e5+;
struct rec{
int pre,to,w;
}a[N<<];
int n,ans,head[N],tot;
int f[N][],c[];
void adde(int u,int v,int w)
{
a[++tot].pre=head[u];
a[tot].to=v;
a[tot].w=w;
head[u]=tot;
}
void dfs(int u,int fath)
{
f[u][]=;
for (int i=head[u];i;i=a[i].pre){
int v=a[i].to;
if (v==fath) continue;
dfs(v,u);
for (int j=;j<=;j++) c[(j+a[i].w)%]=f[v][j];
for (int j=;j<=;j++) ans+=c[j]*f[u][(-j)%];
for (int j=;j<=;j++) f[u][j]+=c[j];
}
}
signed main()
{
scanf("%lld",&n);
int u,v,w;
for (int i=;i<n;i++)
scanf("%lld%lld%lld",&u,&v,&w),
adde(u,v,w),adde(v,u,w);
dfs(,);
cout<<ans;
return ;
}
seven(TreeDp).cpp
( The End )
HGOI 20190303 题解的更多相关文章
- HGOI 20181028 题解
HGOI 20181028(复赛备考) /* 真是暴力的一天,最后一题MLE?由于数组开得太大了!!! 270滚粗 考场上好像智商高了很多?!(假的) */ sol:暴力求解,然后没有数据范围吐槽一下 ...
- HGOI 20190310 题解
/* 又是又双叒叕WA的一天... 我太弱鸡了... 今天上午打了4道CF */ Problem 1 meaning 给出q组询问,求下列函数的值$ f(a) = \max\limits_{0 < ...
- HGOI 20180224 题解
/* The Most Important Things: ljc chat with fyh on QQTa说期末考Ta数学74分感觉不好但是我觉得fyh是地表最强的鸭~~(of course en ...
- HGOI 20190218 题解
/* 又是AK局... hjc又双叒叕AK了... Hmmm...我侥幸 */ Problem A card 给出无序序列a[]可以选择一个数插入到合适的位置作为一次操作,至少多少次操作后可以把序列变 ...
- HGOI 20190217 题解
/* for me,开训第一天 /beacuse 文化课太差被抓去补文化课了... 看一眼题 : AK局? 但是,Wa on test #10 in problem C 290! (就差那么一咪咪) ...
- HGOI 20181103 题解
problem:把一个可重集分成两个互异的不为空集合,两个集合里面的数相乘的gcd为1(将集合中所有元素的质因数没有交集) solution:显然本题并不是那么容易啊!考场上想了好久.. 其实转化为上 ...
- HGOI 20181101题解
/* 又是爆0的一天(不知道今年高考难不难,反正今天(信息学)真的难!) */ solution:对于两个数相加,有一个显然的结论就是要么不进位(相对于位数大的),要么(进最多一位) 然后对于整个数组 ...
- HGOI 20191108 题解
Problem A 新婚快乐 一条路,被$n$个红绿灯划分成$n+1$段,从前到后一次给出每一段的长度$l_i$,每走$1$的长度需要$1$分钟. 一开始所有红绿灯都是绿色的,$g$分钟后所有红绿灯变 ...
- HGOI 20191107 题解
Problem A 树状数组 给出下列$C++$代码: 设区间加操作$modify(l,r)$为调用两次$update(r,1)$和$update(l-1,-1)$ 设$f(l,r)$表示在初始$cn ...
随机推荐
- Android自动化测试之Monkeyrunner使用方法及实例
目前Android SDK里自带的现成的测试工具有monkey 和 monkeyrunner两个.大家别看这俩兄弟名字相像,但其实是完完全全不同的两个工具,应用在不同的测试领域.总的来说,monkey ...
- 调用不同目录类的protected构造器
一.问题 二.分析 调用不同目录类的protected构造器,IDE报错. 二.解决办法: 后面添加一个{}就可以了
- SqlServer 案例:已有汽车每日行驶里程数据,计算其每日增量
需求说明 某公司某项业务,需要获得用户每日行车里程数.已知能获得该车每日提交的总里程数,如何通过 T-SQL 来获得其每日增量里程? 解决方案 首选需要对数据进行编号,利用开窗函数 OVER() 实现 ...
- 【下一代核心技术DevOps】:(三)私有代码库阿里云Git使用
1. 引言 使用DevOps肯定离不开和代码的集成.所以要想跑通整套流程,代码库的选型也是非常重要的.否则无法实现持续集成.目前比较常用的代码管理有SVN和GIt 如果还使用SVN的,建议尽早迁移到G ...
- markdown操作手册
**1.标题** # h1 h1自带分割线 ## h2 ### h3 #### h4 ##### h5 ###### h6 **2.圆点** - 圆点 **3.分割线,-和*都可以** --- *** ...
- SKINNY加密算法详解(无代码,仅加密)
原作者论文请参考<The SKINNY Family of Block Ciphers and Its Low-Latency Variant MANTIS> 地址为:https://li ...
- Mysql抓包工具 - MySQL Sniffer 使用小结 (含带general_log日志)
在mysql运维工作中,一般会使用tcpdump做一些分析(直接读分析日志比较难以看明白,在数据库连接值高时使用):对于mysql实时的连接监控分析,通常会使用"mysqladmin/sho ...
- 个人阅读作业 final
前两次阅读作业链接: http://www.cnblogs.com/SteelPillar/p/4027877.html http://www.cnblogs.com/SteelPillar/p/40 ...
- M2项目测试
更为详细的测试报告,我们会在后续整理出来. 在M1的基础上,我们新增加了两个个数据表来存放问答对以及标签信息的表:C705question表 与 tag表 第二次迭代中,我们积极地同第三组沟通,了解到 ...
- Linux养成笔记
教程来自慕课网@Tony老师的课程 Linux简介 Linux发展史 Andrew S. Tanenbaum为了给学生讲课,买了一个Unix操作系统,参考他开发了Minix,并开放代码作为大学研究,2 ...