清明欢乐赛(USACO选题)
T1. [USACO19JAN] Redistricting P
- 思路:
这种每次选出段长有个上限\(k\)的常常是和单调队列有关。
这里是单调队列优化dp
不过一开始想不太清有什么单调性。
发现每次的贡献为\(0/1\)
因此如果\(i<j\)且\(dp_i<dp_j\)。\(i\)最多就和\(j\)一样贡献直接删去。
如果\(dp_i=dp_j\)你需要考虑中间段的贡献,决定是否删。
不过总之我们的目的是让优劣性单调递减(队首最优)
如果段\([i+1..j]\)中\(cnt('H')>cnt('G')\)在\(i\)能取到时一定会比\(j\)优,所以保留\(i\)
否则直接弹出\(i\) - code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
char s[N];
int dp[N],sh[N],sg[N];
int Q[N],hd=1,tl;
bool W(int l,int r) {
return (sg[r]-sg[l]>=sh[r]-sh[l]);
}
bool cmp(int x,int y) {return dp[x]==dp[y]?W(x,y):dp[x]>dp[y];}
int main() {
int n,k,l=1,r;
scanf("%d%d",&n,&k);
scanf("%s",s+1);
for(int i=1;i<=n;i++)sh[i]=sh[i-1]+(s[i]=='H'),sg[i]=sg[i-1]+(s[i]=='G');
dp[0]=0;Q[++tl]=0;
for(int i=1;i<=n;i++) {
while(hd<=tl&&Q[hd]+k<i) hd++;
dp[i]=dp[Q[hd]]+W(Q[hd],i);
while(hd<=tl&&cmp(Q[tl],i)) tl--;
Q[++tl]=i;
}
printf("%d\n",dp[n]);
return 0;
}
T2.[USACO20JAN] Cave Paintings P
- 思路:
并查集
最简单的思路就是从下往上灌水。
如果某两个节点在\(i+1\)行不连通,在第\(i\)行联通。然后这两个节点在\(i……n\)时是独立的。(如果独立就相当于乘法原理,不独立就会合成同类一起作贡献)
推广到带权并查集,合并两个联通块价值为它们乘积+1
当然我考场上想复杂了。
因为我一直往图论上套(思维僵化,犯了跟noionline一样的错误)。
不过还是搞了很久,我把它缩点(如果两个点通过往下、左、右走能联通就合成一个点一起作贡献)
可能因为我这种做法每行横向一段是手动缩点(而不是并查集),就跑的飞快。
缩点后成为森林,树上dp。(别忘了记录完起点)
具体缩点写法,(和正常写法很像)到能合并的时机时,加一个新点代表(合并后的点),连向所有每个都能到的下层的节点(下层的森林上的节点已经构造好了)。 - code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+5;
const int M=2e6+5;
const int mod=1e9+7;
int n,m,st[M],tp;
char s[N][N];
int tt[N],ct,Ll,Rl,fa[M],nxt[M],to[M],head[M],ecnt,nd,pos[N][N];
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
int gt_fa(int u) {return fa[u]==u?u:fa[u]=gt_fa(fa[u]);}
void Union(int u,int v) {fa[gt_fa(u)]=gt_fa(v);}
int rt[M];
void Build() {
bool bg=0;
for(int i=n;i;i--) {
// printf("!%d\n",i);
if(!bg) {
for(int j=1;j<=m;j++) {
if(s[i][j]=='.') {
++nd;fa[nd]=nd;pos[i][j]=nd;
while(s[i][j+1]=='.') {
j++;pos[i][j]=nd;
}
}
}
if(nd){Ll=1;Rl=nd;bg=1;}
continue;
}
for(int j=1;j<=m;j++) {
if(s[i][j]=='.') {
++nd;fa[nd]=nd;pos[i][j]=nd; //tmp node
if(pos[i+1][j])Union(nd,pos[i+1][j]);
while(s[i][j+1]=='.') {
j++;pos[i][j]=nd;if(pos[i+1][j])Union(nd,pos[i+1][j]);
}
}
}
// new node
int nwR=nd;
ct=0;
for(int j=1;j<=m;j++) {
if(s[i][j]=='#') continue;
int x=gt_fa(pos[i][j]);
if(!rt[x]) {rt[x]=++nd;tt[++ct]=x;fa[nd]=nd;}
pos[i][j]=rt[x];
}
for(int j=Ll;j<=Rl;j++) {
int x=gt_fa(j);
if(!rt[x]) st[++tp]=x; //beginning node
else add_edge(rt[x],j);
// printf("%d(%d) %d\n",rt[x],x,j);
}
Ll=nwR+1;Rl=nd;
while(ct) {rt[tt[ct]]=0;ct--;}
// printf("[%d,%d]\n",Ll,Rl);
}
}
ll dp[M];
void DP(int u) {
dp[u]=1;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
DP(v);
dp[u]=dp[u]*dp[v]%mod;
}
dp[u]++;
}
int main() {
// int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
Build();
ll ans=1;
while(tp) {
int i=st[tp--];
DP(i);ans=ans*dp[i]%mod;
}
printf("%lld",ans);
return 0;
}
T3.[USACO20JAN] Non-Decreasing Subsequences P
题意:给每一个长为\(n\)的数列,每个数的上限是\(k\),\(Q\)次问\([l_i,r_i]\)内的最长不降子序列的方案数
思路:
动态dp
线段树维护\(z[x][y]\)表示上升子序列的权值为从\(x\)到\(y\)的方案数。
\(O(q\log_2{n}\ k^2)\)
难写又慢,而且发现玄机(\(q\)是\(n\)的\(20\)倍)
想要把询问\(q\)复杂度,转到\(n\)上。接下来用到:
中轴分治(自己乱创的,类似cdq)
即分治时每次处理跨过\(mid=(l+r)/2\)的区间。
然后这个区间的结果为\(mid\)左边的部分和右边的部分合并起来。
因此要预处理从\(mid\)往左的答案,从\(mid\)往右的答案。
如果处理和查询的每一步是\(O(1)\)的话,这个算法的复杂度是\(O(nlog_2n+q)\)
这样就能把\(q\)的复杂度转化到\(n\)上呢。
合并显然需要:
\(G[i][y]\):(右半边)开始的值为\(y\),前缀\(i\)的总方案数
\(F[i][y]\):(左半边)结束的值为\(y\),后缀\(i\)的总方案数
这里只说右半边(\(G\))的做法,\(F\)同理
方便\(G\)的转移,我们定义\(g[x][y]\)为上面的\(z[x][y]\)的意思
然后每次新添加\(a_i\)时:只有\(g[y][a_i]\ \ (1<=y<=a_i)\)会受影响。
要找前面的一个\(z<=a_i\)
\(g[y][a_i]+=\sum\limits_{z=1}^{a_i}g[y][z]\)
还要考虑\(a_i\)独立为一个子序列
\(g[a_i][a_i]++\)
这样\(O(k^2)\)处理了\(g\)
\(G[i][y]=\sum\limits_{z=y}^kg[y][z]\) 知道开头\(y\)枚举结尾
\(O(k^2)\)而且这个跑的挺满的,实际需要\(O(k)\)就可以啦。
\(G[i-1][y]\)相比\(G[i][y]\)只有在\(y<=a_i\)时,\(g[y][a_i]\)的改变会影响\(G[i][y]\),此时加上\(g[y][a_i]\)的变化量即可。
总复杂度\(nlognk^2+qk\)而且肯定是跑不满的。
我翻了翻题解,题解搞了数据结构来优化到\(nklognlogk\)实际上因为数据结构卡死了上限还跑的比我慢很多。code
戳我
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
const int M=N>>2;
const int K=21;
const int mod=1e9+7;
int a[N],b[N],q,n,k,rev[N],A[N],t1[N],t2[N];
//g[x][y]:(val)[x...y]; G[i][y]:(val)[y...],pre(pos) i
ll g[K][K],f[K][K],G[M][K],F[M][K],sG[M][K],ans[N];
struct query {int l,r;}Q[N];
void gt_gG(int l,int r) {
g[a[l]][a[l]]=G[l][a[l]]=1;
for(int i=l+1;i<=r;i++) {
int ai=a[i];
for(int y=1;y<=ai;y++) {
G[i][y]=G[i-1][y]-g[y][ai];
ll tmp=g[y][ai]+(y==ai);
for(int z=y;z<=ai;z++) tmp+=g[y][z];
g[y][ai]=(tmp%=mod);
G[i][y]=(G[i][y]+tmp)%mod;
}
for(int y=ai+1;y<=k;y++) {G[i][y]=G[i-1][y];}
}
}
void gt_fF(int l,int r) {
f[b[l]][b[l]]=F[l][b[l]]=1;
for(int i=l+1;i<=r;i++) {
int bi=b[i];
for(int y=bi;y<=k;y++) {
F[i][y]=F[i-1][y]-f[bi][y];
ll tmp=f[bi][y]+(y==bi);
for(int z=bi;z<=y;z++) tmp+=f[z][y];
f[bi][y]=(tmp%=mod);
F[i][y]=(F[i][y]+tmp)%mod;
}
for(int y=1;y<bi;y++)F[i][y]=F[i-1][y];
}
}
void Clear(int l,int r,int L,int R) {
for(int i=1;i<=k;i++)for(int j=1;j<=k;j++)g[i][j]=f[i][j]=0;
for(int i=l;i<=r;i++)for(int j=1;j<=k;j++)F[i][j]=0;
for(int i=L;i<=R;i++)for(int j=1;j<=k;j++)sG[i][j]=G[i][j]=0;
}
ll SG(int i,int j) {
if(sG[i][j])return sG[i][j];
for(int y=1;y<=j;y++) sG[i][j]=(sG[i][j]+G[i][y])%mod;
return sG[i][j];
}
void solve(int l,int r,int L,int R) {
int mid=(l+r)>>1;
gt_gG(mid+1,r);gt_fF(rev[mid],rev[l]);
int c1=L,c2=R;
for(int i=L;i<=R;i++) {
int x=A[i];
if(Q[x].l==Q[x].r) ans[x]=2;
else if(Q[x].l>mid) t2[c2--]=x;
else if(Q[x].r<=mid) t1[c1++]=x;
else {
int st=rev[Q[x].l],ed=Q[x].r;
ans[x]=1+SG(ed,k);
for(int yl=1;yl<=k;yl++) {
ans[x]=(ans[x]+F[st][yl]*(SG(ed,k)-SG(ed,yl-1)+1))%mod;
}
}
}
Clear(rev[mid],rev[l],mid+1,r);
if(c1!=L) {
for(int i=L;i<c1;i++) A[i]=t1[i];
solve(l,mid,L,c1-1);
}
if(c2!=R) {
for(int i=R;i>c2;i--) A[i]=t2[i];
solve(mid+1,r,c2+1,R);
}
}
int main() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) {rev[i]=n-i+1;b[rev[i]]=a[i];}
// for(int i=1;i<=n;i++)printf("%d ",b[i]);puts("");
scanf("%d",&q);
for(int i=1;i<=q;i++) {scanf("%d%d",&Q[i].l,&Q[i].r);A[i]=i;}
solve(1,n,1,q);
for(int i=1;i<=q;i++) printf("%lld\n",(ans[i]+mod)%mod);
return 0;
}
最新文章
- odoo 人力资源工资计算拓展
- 基于Bootstrap、Jquery的自适应导航栏
- Nginx内置变量以及日志格式变量参数详解
- 利用正则表达式作为string.split seprator
- jQuery EasyUI 提示框(Messager)用法
- PAT 1017. Queueing at Bank
- TWinControl的DoubleBuffered属性的作用与举例
- Actor模型
- Java之对象序列化和反序列化
- Flex Cairngrom框架浅浅印象
- JDBC第二篇--【PreparedStatment、批处理、处理二进制、自动主键、调用存储过程、函数】
- [转载]binlog归档
- centos7 安装php gd库
- scrapy 动态IP、随机UA、验证码
- springbank 开发日志 SpringMVC是如何找到handler找到对应的方法并执行的
- 使用RStudio远程连接MySQL
- Linux基础命令---arch
- 『cs231n』卷积神经网络工程实践技巧_下
- PREV-6_蓝桥杯_翻硬币
- 继承Tcalendar控件,让当天日期醒目显示
热门文章
- vue全家桶+axios+jsonp+es6 仿肤君试用小程序
- 将HTML页面转换为PDF文件并导出
- 论文阅读-Temporal Phenotyping from Longitudinal Electronic Health Records: A Graph Based Framework
- PAT A1001 A+B Format
- FastAPI(六十七)实战开发《在线课程学习系统》接口开发--用户登陆接口开发
- Dart语言基础
- Java的虚拟线程(协程)特性开启预览阶段,多线程开发的难度将大大降低
- 安卓记账本开发学习day10
- 【PostgreSQL】入门学习笔记
- web服务报错类型