AGC033 D~F——[ 值放到角标的DP ][ 思路+DP ][ 思路 ]

Wesley13
• 阅读 673

地址:https://atcoder.jp/contests/agc033/

D Complexity

dp[ i ][ j ][ k ][ l ] 表示左上角是 ( i , j ) 、右下角是 ( k , l ) 的矩阵的最小代价。

注意到答案是 log(n) + log(m) 级别的,因为每次从中间分, log 次之后就变成一行/列,log(n)+log(m)次就变成 1*1 的格子,代价是 0 。

所以把值和角标互换,dp[ i ][ j1 ][ j2 ][ k ] 表示左上角是 ( i , j1 ) 、右上角是 ( i , j2 ) 、用 k 的代价,往下最长能延伸到哪行。

转移的时候考虑横着切与竖着切。令 d = dp[ i ][ j1 ][ j2 ][ k ] ,首先有 d = dp [ dp[ i ][ j1 ][ j2 ][ k-1 ] ] [ j1 ][ j2 ][ k-1 ] ;

然后考虑 j3 满足 j1 <= j3 < j2 ,使得 [ j1 , j3 ] 和 [ j3+1 , j2 ] 就是分出的两部分;

如果 dp[ i ][ j1 ][ j3 ][ k-1 ] 和 dp[ i ][ j3+1 ][ j2 ][ k-1 ] 有一个是 < d 的,那么说明在这里切之后,两边有一个部分的代价 > k-1 ;

考虑二分找出最大的 min( dp[ i ][ j1 ][ j3 ][ k-1 ] , dp[ i ][ j3+1 ][ j2 ][ k-1 ] ) 。因为固定一条边之后,矩形越大,代价越大,所以根据两个值的大小关系来二分即可。

AGC033 D~F——[ 值放到角标的DP ][ 思路+DP ][ 思路 ] AGC033 D~F——[ 值放到角标的DP ][ 思路+DP ][ 思路 ]

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int Mx(int a,int b){return a>b?a:b;}
int Mn(int a,int b){return a<b?a:b;}
const int N=190;
int n,m,sm[N][N],dp[2][N][N][N];
char ch[N][N];
bool chk(int x1,int y1,int x2,int y2)
{
  int s=sm[x2][y2]-sm[x2][y1-1]-sm[x1-1][y2]+sm[x1-1][y1-1];
  return (!s)||s==(x2-x1+1)*(y2-y1+1);
}
int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)
    scanf("%s",ch[i]+1);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)sm[i][j]=sm[i][j-1]+(ch[i][j]=='.');
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)sm[i][j]+=sm[i-1][j];
  for(int i=1;i<=n;i++)
    for(int j1=1;j1<=m;j1++)
      for(int j2=j1;j2<=m;j2++)
    {
      int l=i,r=n,ans=i-1;
      while(l<=r)
        {
          int mid=l+r>>1;
          if(chk(i,j1,mid,j2))ans=mid,l=mid+1;
          else r=mid-1;
        }
      dp[0][i][j1][j2]=ans;
    }
  if(dp[0][1][1][m]==n){puts("0");return 0;}
  bool u=1,v=0;
  for(int t=1;t<=16;t++,swap(u,v))
    {
      for(int i=1;i<=n;i++)
    for(int j1=1;j1<=m;j1++)
      for(int j2=j1;j2<=m;j2++)
        {
          int d=dp[v][dp[v][i][j1][j2]+1][j1][j2];
          if(dp[v][i][j1][j2]==n)
        { dp[u][i][j1][j2]=n;continue;}
          int l=j1,r=j2-1;
          while(l<=r)
        {
          int mid=l+r>>1;
          int t1=dp[v][i][j1][mid],t2=dp[v][i][mid+1][j2];
          d=Mx(d,Mn(t1,t2));
          if(t1>=t2)l=mid+1; else r=mid-1;
        }
          dp[u][i][j1][j2]=d;
        }
      if(dp[u][1][1][m]==n)
    {printf("%d\n",t);return 0;}
    }
}

View Code

E Go around a Circle

题解:https://blog.csdn.net/yzyyylx/article/details/89839025

关键是把 “有一种颜色不能相邻” 和 “长度是奇数” 的限制用 “形如 RRR..RRB 的段” 的角度来看,通过 “ 长度/2 ” 来去掉第二个条件。

注意最后一个连续段如果是和第一个字符相同的字符,不会对长度造成限制。注意是全局的最后一个连续段,而不是该种字符的最后一段。

AGC033 D~F——[ 值放到角标的DP ][ 思路+DP ][ 思路 ] AGC033 D~F——[ 值放到角标的DP ][ 思路+DP ][ 思路 ]

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int Mn(int a,int b){return a<b?a:b;}
const int N=2e5+5,mod=1e9+7;
int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;}

int n,m,lm,dp[N],sm[N],ans,g[N];
char s[N];
void solve()
{
  g[0]=sm[0]=1;
  for(int i=1;i<=n;i++)
    {
      if(i>=2)g[i]=sm[i-2];
      sm[i]=upt(sm[i-1]+g[i]);
    }
  int ans=1;//ans=1 for all one color
  for(int i=2;i<=n;i++)
    ans=(ans+(ll)i*g[n-i])%mod;
  printf("%d\n",ans);
}
int main()
{
  scanf("%d%d",&n,&m);
  scanf("%s",s+1); bool fg=0;
  for(int i=1;i<=m;i++)
    if(s[i]!=s[1]){fg=1;break;}
  if(!fg){solve();return 0;}
  if(n&1){puts("0");return 0;}
  lm=n+1;
  for(int i=1;i<=m;i++)
    if(s[i]==s[1])//== not !=
      {
    int j=i;
    while(j+1<=m&&s[j+1]==s[j])j++;
    swap(i,j); j=i-j+1;
    if(i-j==0) lm=Mn(lm,j+((j&1)==0));
    else if((j&1)&&i!=m)lm=Mn(lm,j);//i!=m not lst
    //else if((j&1)&&i!=lst)lm=Mn(lm,j);
      }
  n>>=1; lm=(lm+1)>>1; dp[0]=sm[0]=1;
  for(int i=1;i<=n;i++)
    {
      dp[i]=sm[i-1]; if(i>lm)dp[i]=upt(dp[i]-sm[i-lm-1]);
      sm[i]=upt(sm[i-1]+dp[i]);
    }
  int ans=0;
  for(int i=1;i<=lm;i++)//lm not n
    ans=(ans+(ll)i*2*dp[n-i])%mod;
  printf("%d\n",ans);
  return 0;
}

View Code

 F Adding Edges

题解代码看了这个:https://atcoder.jp/contests/agc033/submissions/5406519

但还是不太明白这道题是怎么回事……

算法流程大概是:

  考虑把图上的边一条一条加进去,一边加一边让它尽量贴在树上;就是如果有边 ( a, b ) 和 ( a, c ) 且在树上有 a->b->c 的链(a,b,c可以不相邻),就把 ( a, c ) 的边改成 ( b, c ) 的边。

  然后可以认为一个点沿图上的边、顺着树上的链能走到的点都是它可以连边的对象;走不到的就是不能连边的。

  所以主要考虑怎么把第一行的那个操作实现。

  考虑图中加入一条边 ( a, b ) ;把树上 a -> b 的路径找出来; a 和 b 都沿图上的边、顺着树上的链往对方走过去(别走得过了,可以先走 a ,然后 b 走的时候不要超过 a 的位置);

  如果 a 走到路径的另一端,或者 a 和 b 停下之后发现它们在图上已经有边了,就不管了;

  否则就给图连上 ( a, b ) ;然后要考虑这条边是否导致一些边需要修改;需要修改的边一定有一个端点是 a 或者 b ,设一条边 ( a, x ) 要被删掉,需要满足树上有 a->b->x 的链(a,b,x可以不相邻),那么就要把 ( a, x ) 改成 ( b, x ) ;改的操作就是删边,然后递归这个“加边”操作来加入新边。

  把一个点的出边用 set 存就能方便地删边了。

  判断一个点 cr 在两个点 s , t 的路径上,可以通过判断 dis( s, t ) == dis( s, cr ) + dis( cr, t ) 。

  尚不明白正确性或者复杂度……

AGC033 D~F——[ 值放到角标的DP ][ 思路+DP ][ 思路 ] AGC033 D~F——[ 值放到角标的DP ][ 思路+DP ][ 思路 ]

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
const int N=2005;
int n,m,hd[N],xnt,to[N<<1],nxt[N<<1],fa[N][N],dis[N][N];
int p[N<<1],tot,ans; bool vis[N][N];
set<int> cd[N];
set<int>::iterator it;
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void ini_dfs(int cr,int rt,int lj)
{
  dis[rt][cr]=lj;
  for(int i=hd[cr],v;i;i=nxt[i])
    if(!fa[rt][v=to[i]])
      { fa[rt][v]=cr; ini_dfs(v,rt,lj+1);}
}
void fnd_path(int x,int y)
{
  tot=0; p[++tot]=x;
  while(x!=y){ x=fa[y][x]; p[++tot]=x;}
}
bool on_path(int cr,int s,int t)
{return dis[s][t]==dis[s][cr]+dis[cr][t];}
void solve(int x,int y)
{
  fnd_path(x,y); int s=x,t=y,tmp=1;
  for(int i=1;i<=tot;i++) if(vis[s][p[i]])s=p[i],tmp=i;
  for(int i=tot;i>=tmp;i--) if(vis[t][p[i]])t=p[i];//>=tmp
  if(s==y||vis[s][t])return;
  int tp[N],tt=0;//here not use p[]!!!
  for(it=cd[s].begin();it!=cd[s].end();it++)
    if(on_path(t,(*it),s))tp[++tt]=(*it);
  int bj=tt;
  for(it=cd[t].begin();it!=cd[t].end();it++)
    if(on_path(s,(*it),t))tp[++tt]=(*it);
  vis[s][t]=vis[t][s]=1;
  cd[s].insert(t); cd[t].insert(s);
  for(int i=1;i<=bj;i++)
    {
      int v=tp[i]; vis[s][v]=vis[v][s]=0;
      cd[s].erase(v); cd[v].erase(s);
      solve(t,v);
    }
  for(int i=bj+1;i<=tt;i++)
    {
      int v=tp[i]; vis[t][v]=vis[v][t]=0;
      cd[t].erase(v); cd[v].erase(t);
      solve(s,v);
    }
}
void dfs(int cr,int rt)
{
  set<int>::iterator it2;//don't use it!!!!!
  for(it2=cd[cr].begin();it2!=cd[cr].end();it2++)
    if(on_path(cr,rt,(*it2))&&!vis[rt][*it2])
      vis[rt][*it2]=1,ans++,dfs((*it2),rt);
}
int main()
{
  n=rdn();m=rdn();
  for(int i=1,u,v;i<n;i++)
    { u=rdn();v=rdn();add(u,v);add(v,u);}
  for(int i=1;i<=n;i++)
    fa[i][i]=-1,ini_dfs(i,i,0);
  for(int i=1,u,v;i<=m;i++)
    { u=rdn();v=rdn();solve(u,v);}
  memset(vis,0,sizeof vis);
  for(int i=1;i<=n;i++) dfs(i,i);
  printf("%d\n",ans/2);
  return 0;
}

View Code

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这