Codeforces Round #857 Div.1 1801 E F 题解

Posted LegendStane

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces Round #857 Div.1 1801 E F 题解相关的知识,希望对你有一定的参考价值。

点我看题

1801 E. Gasoline prices

先来看这样一道题:一个长为n的字符串,每个字符都是一个英文小写字母。有q个限制,每个限制形如\\(x\\ y\\ len\\),表示从位置x与从位置y开始的长度为len的子串相同。要求出满足所有限制的字符串的数量。这题和本题1801E是很像的,只是1801E要对所有限制的每个前缀都求答案,且问题移到了树上。

来看看这个字符串题的做法。先对这个串建立st表,\\(f_i,j\\)表示左端点在i,长度为\\(2^j\\)的子串。我们对st表的每层(\\(f_*,j\\)为一层)维护一个并查集,如果\\(f_a,j\\)\\(f_b,j\\)在第j层的并查集中被连接,就表示从a开始和从b开始的长度为\\(2^j\\)的子串必须相等。对于每条限制,令\\(lev=\\lfloor lg(len) \\rfloor\\),在第lev层的并查集中把\\(f_x,lev与f_y,lev\\)连接,把\\(f_x+len-2^lev,lev与f_y+len-2^lev,lev\\)连接。处理完所有的限制后,从大到小枚举所有j,然后枚举i,令\\(f_i,j\\)在第j层并查集中的根为\\(f_k,j\\),那么就在第j-1层中连接\\(f_i,j-1和f_k,j-1\\),以及\\(f_i+2^j-1,j-1和f_k+2^j-1,j-1\\)。这样就把所有限制信息都像线段树一样pushdown到了第0层,只要检查第0层的并查集连通块个数就知道有多少个等价类了。答案是\\(26^等价类个数\\)

然后这个字符串题如果要对q个限制的每个前缀都求答案的话也是很容易的,可以每次添加一个限制后直接不断pushdown到第0层,并在修改第0层时维护连通块个数。每一层的并查集最多被修改n次(如果发现并查集中当前要连接的两个位置已经连通,就直接退出,不继续pushdown),如果并查集复杂度算\\(logn\\)的话,总复杂度\\(O(nlog^2n)\\)

再回到原问题。其实可以直接把上面的st表搬到树上,\\(f_i,j\\)表示从点i向祖先方向的前\\(2^j\\)个点构成的链,\\(g_i,j\\)表示从点i向祖先方向的前\\(2^j\\)个点构成的链reverse后的结果,第j层的并查集中包含\\(f_*,j\\)\\(g_*,j\\)中的所有元素(其中第0层只包括\\(f_*,0\\)中的元素),可以给它们从1到2n编个号。对于一个限制,我们要求两条链上的点的值一一对应相等,仍然是在并查集中直接合并,只是这里要以两条链的LCA为分界分成3部分,其中一部分是把一个\\(f_*,*和一个g_*,*\\)合并,还有两部分是把两个f合并。并查集中的每一层最多被合并\\(2n\\)次,所以总复杂度仍然是\\(O(nlog^2n)\\)(有点卡常)。如果不仔细想无脑写的话很容易写得很烦,下面的代码参考了platelet的写法

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define fi first
#define se second
#define pb push_back
#define mpr make_pair

void fileio()

  #ifdef LGS
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
  #endif

void termin()

  #ifdef LGS
  std::cout<<"\\n\\nEXECUTION TERMINATED";
  #endif
  exit(0);


using namespace std;

const LL MOD=1e9+7;

LL qpow(LL x,LL a)

	LL res=x,ret=1;
	while(a>0)
	
		if(a&1) (ret*=res)%=MOD;
		a>>=1;
		(res*=res)%=MOD;
	
	return ret;


LL n,q,par[200010][20],l[200010],r[200010],dep[200010];
LL ans=1;
vector <LL> g[200010];

void dfs(LL pos,LL d)

  dep[pos]=d;
  rep(i,g[pos].size()) dfs(g[pos][i],d+1);


LL getLCA(LL x,LL y)

  for(int i=18;i>=0;--i) if(par[x][i]>0&&dep[par[x][i]]>=dep[y]) x=par[x][i];
  for(int i=18;i>=0;--i) if(par[y][i]>0&&dep[par[y][i]]>=dep[x]) y=par[y][i];
  if(x==y) return x;
  for(int i=18;i>=0;--i) if(par[x][i]!=par[y][i]) x=par[x][i],y=par[y][i];
  return par[x][0];

LL getAnces(LL x,LL y)rep(i,19) if(y&(1<<i)) x=par[x][i];return x;

struct Dsu

  LL fa[400010];
  Dsu()rep(i,400005) fa[i]=i;
  LL find(LL x)return fa[x]==x ? x:fa[x]=find(fa[x]);
  bool merge(LL x,LL y,LL lev)
  
    if(find(x)==find(y)) return false;
    if(lev==0)
    
      (ans*=qpow((r[fa[x]]-l[fa[x]]+1)*(r[fa[y]]-l[fa[y]]+1)%MOD,MOD-2))%=MOD;
      (ans*=max(min(r[fa[x]],r[fa[y]])-max(l[fa[x]],l[fa[y]])+1,0LL))%=MOD;
      r[fa[y]]=min(r[fa[y]],r[fa[x]]);l[fa[y]]=max(l[fa[y]],l[fa[x]]);
    
    fa[find(x)]=find(y);
    return true;
  
dsu[20];
void merge(LL x,LL y,LL lev)

  if(!dsu[lev].merge(x,y,lev)||lev==0) return;
  merge(x,y,lev-1);merge(par[x][lev-1],par[y][lev-1],lev-1);

void mergeR(LL x,LL y,LL lev)

  if(!dsu[lev].merge(x,y+(lev==0 ? 0:n),lev)||lev==0) return;
  mergeR(x,par[y][lev-1],lev-1);mergeR(par[x][lev-1],y,lev-1);

void mergePath(LL x,LL y,LL len)

  LL lev=__lg(len);
  merge(x,y,lev);
  merge(getAnces(x,len-(1<<lev)),getAnces(y,len-(1<<lev)),lev);

void mergePathR(LL x,LL y,LL len)

  LL lev=__lg(len);
  mergeR(x,getAnces(y,len-(1<<lev)),lev);
  mergeR(getAnces(x,len-(1<<lev)),y,lev);


int main()

  fileio();
  
  cin>>n;
  for(int i=2;i<=n;++i) scanf("%lld",&par[i][0]),g[par[i][0]].pb(i);
  rep(i,19) repn(j,n) par[j][i+1]=par[par[j][i]][i];
  repn(i,n) scanf("%lld %lld",&l[i],&r[i]),(ans*=(r[i]-l[i]+1))%=MOD;
  dfs(1,0);

  cin>>q;
  rep(qn,q)
  
    LL a,b,c,d,ab,cd;
    scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
    if(ans==0)
    
      puts("0");
      continue;
    
    ab=getLCA(a,b);cd=getLCA(c,d);
    if(dep[a]-dep[ab]<dep[c]-dep[cd]) swap(a,c),swap(b,d),swap(ab,cd);
    LL tot=dep[a]+dep[b]-dep[ab]*2+1,l1=dep[c]-dep[cd]+1,l2=(dep[a]-dep[ab])-(dep[c]-dep[cd]),l3=tot-l1-l2;
    if(l1) mergePath(a,c,l1);
    if(l2) mergePathR(getAnces(a,l1),getAnces(d,l3),l2);
    if(l3) mergePath(b,d,l3);
    printf("%lld\\n",ans);
  

  termin();


1801 F. Another n-dimensional chocolate bar

\\(k\\le 1e7\\),所以可能会想到去枚举把b从小到大排序,然后去掉所有1之后得到的那个序列(最多有\\(logk\\)个元素)。但在这些枚举出的序列中并不是所有都可能成为最优的,如果我们可以把其中的一个数-1,使得序列所有元素的乘积仍然\\(\\ge k\\),那它就肯定不是最优的。这样一来就大大减少了可能序列的个数。

进一步看看能不能枚举一些更简单的东西。假设当前要求\\(\\prod b_i\\ge k\'\\),我们看看\\(b_1\\)可能的取值有哪些。令枚举出的\\(b_1\\)的值为\\(v\\)\\(v\'=\\lceil \\frac k\'v \\rceil\\)。则必须满足\\(\\lceil \\frac k\'v\' \\rceil=v\\),否则把v减1,此时的\\(v和v\'\\)仍然满足条件,显然更优。在枚举v时,只要枚举所有满足\\(v\\le v\'\\)的v就能找到所有可能的\\((v,v\')\\)了,因为\\(v和v\'\\)也可以交换。我们用暴搜预处理所有可能出现的\\(k\'\\)及其对应的可能出现的\\((v,v\')\\),方法为先对输入的k搜索可能的\\(v和v\'\\),然后再把\\(v和v\'\\)作为可能的\\(k\'\\)继续搜索递归下去(需要记忆化),具体实现方法见代码。搜出来后会发现可能的\\(k\'\\)只有7000种左右,可能的\\((v,v\')\\)总数则不超过\\(10^6\\)个。令\\(s_i\\)表示当i作为上面的\\(k\'\\)时,所有\\((v,v\')\\)的集合。

所以可以令\\(f_i,j\\)表示处理到a中的第i个数,要求\\(\\prod_p=i^n b_p\\ge j\\)时的最优答案。对\\(f_i,j\\)进行主动转移时只要枚举\\(s_j\\)中的所有元素就行了。发现这个数组的第一维完全没用,可以删掉,所以空间也没问题了。

时间复杂度\\(O(10^6n)\\)左右。

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <LL,LL>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair

void fileio()

  #ifdef LGS
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
  #endif

void termin()

  #ifdef LGS
  std::cout<<"\\n\\nEXECUTION TERMINATED";
  #endif
  exit(0);


using namespace std;

LL n,k,a[110];
bool vis[10000010];
vector <LL> possi;
vector <pair <LL,pii> > trans;
double dp[10000010],mul[10000010];

void dfs(LL x)

  if(vis[x]) return;
  possi.pb(x);vis[x]=true;
  for(LL dv=1;;++dv)
  
    LL res=(x+dv-1)/dv;
    if(dv>res) break;
    dfs(dv);dfs(res);
    if((x+res-1)/res!=dv) continue;
    if(dv>1) trans.pb(mpr(x,mpr(dv,res)));
    if(dv!=res&&res>1) trans.pb(mpr(x,mpr(res,dv)));
  


int main()

  fileio();
  
  cin>>n>>k;
  dfs(k);
  LL mx=1;
  rep(i,n) scanf("%lld",&a[i]),mx=min(mx*a[i],(LL)1e11);
  if(mx<k)
  
    puts("0");
    termin();
  
  rep(i,10000005) dp[i]=-1;dp[k]=1;
  rep(i,n)
  
    rep(j,possi.size()) mul[possi[j]]=(double)(a[i]/possi[j])/a[i];
    rep(j,trans.size())
    
      LL fr=trans[j].fi,dv=trans[j].se.fi,to=trans[j].se.se;
      //cout<<to<<\' \'<<dv<<\' \'<<fr<<\' \'<<dp[fr]<<" "<<mul[dv]<<endl;
      dp[to]=max(dp[to],dp[fr]*mul[dv]);
     
  
  printf("%.10lf\\n",dp[1]*k);

  termin();

以上是关于Codeforces Round #857 Div.1 1801 E F 题解的主要内容,如果未能解决你的问题,请参考以下文章

Codeforces Round #774 (Div. 2)

Codeforces Round #808 (Div. 1)(A~C)

Codeforces Round #717 (Div. 2)

Codeforces Round #784 (Div. 4)

Codeforces Round #340 (Div. 2) B

Codeforces Round #716 (Div. 2)