The 2018 ICPC Asia-East Continent Final

比赛链接

A.Exotic … Ancient City(思路 并查集)

https://www.bilibili.com/video/av38542305 A

题面在这儿(或者去牛客 EC-Final A上看)。

边权只有\(30\),这其实和边权为\(1\)是等价的。也就是去算边权为\(1\)的边用了多少条,边权为\(2\)的边用了多少条...设只考虑边权为\(1\)的边用了\(x\)条,只考虑边权为\(1,2\)的边一共用了\(y\)条,那么边权为\(2\)的边就用了\(y-x\)条。

其实可以这样算:枚举边权\(w\),将所有边权\(\leq w\)的边加入图中,若成功加入了\(t\)条边(要构造一棵树,可以直接并查集维护),设当前图的点数为\(n\),则边权\(>w\)的边一共用了\(n-1-t\)条。令它们贡献为\(1\),即\(Ans\)+=\(n-1-t\)。

这样\(w\)从\(0\)枚举到\(30\),那边权为\(1\)的边就会被算\(1\)次,边权为\(2\)的边会被算\(2\)次...每一次的贡献都是直接加\(1\),不需要考虑边权。

对于当前要加入的边,我们维护一个大小为\(2n\)的并查集,计算每一层成功加入了多少条边。

对于最初的两层点,只需要并查集维护一下就可以了。考虑第二层与第三层点的连通性与前两层有什么不同,显然在之前连通的点现在还是能连通(加入的边都是一样的),区别就是,第二层的点之间可能在之前连通了。

所以我们可以保留之前的并查集,对于在第二层右侧连通的点对\(u_i+n,v_i+n\),在第三层左侧加入一条边\(u_i,v_i\)。原本的边就不需要保留了。

同样在第三层继续加边\(u,v\)的时候会出现两种情况:\(u,v\)不连通,合并\(u,v\);\(u,v\)已连通,这意味着和上一层相比我们可以少加一条边,令增量\(s[i]\)--。

同样对于在右侧连通的点对\(u_i+n,v_i+n\),再在第四层左侧加入边\(u_i,v_i\)。

当做到某一层没有要加入的边时,答案就不会改变了。

所以我们就可以计算出每一层成功加入边数的增量的增量(即求一遍前缀和后我们可以得到每一层之间相差多少),这是个二阶差分,所以求两遍前缀和就可以得到每一层成功加入的边数了。

枚举边权\(0\sim29\)做\(30\)遍即可。

因为连通性至多改变\(O(n)\)次?所以复杂度是对的,为\(O(30m\alpha(n))\)。

//4.25s	28.3MB(301ms	28176KB)
#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define mp std::make_pair
#define pr std::pair<int,int>
typedef long long LL;
const int N=2e5+5,M=1e5+5; int F[N];
LL Ans[M],s[M];
std::vector<pr> e[30],A,B;
char IN[MAXIN],*SS=IN,*TT=IN; inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
int Find(int x)
{
return x==F[x]?x:F[x]=Find(F[x]);
} int main()
{
// freopen("c.in","r",stdin);
// freopen("c.out","w",stdout); const int n=read(),m=read(),et=read();
for(int i=1; i<=et; ++i)
{
int u=read(),v=read(),w=read();
for(int j=w; j<30; ++j) e[j].push_back(mp(u,v+n));
}
for(int w=0; w<30; ++w)
{
memset(s,0,sizeof s);
for(int i=1; i<=2*n; ++i) F[i]=i;
A.clear();
for(int i=0,l=e[w].size(),u,v; i<l; ++i)
{
u=Find(e[w][i].first), v=Find(e[w][i].second);
if(u>v) std::swap(u,v);
if(u!=v)
{
++s[1], F[u]=v;
if(u>n && v>n) A.push_back(mp(u-n,v-n));
}
}
for(int d=2,l; (l=A.size()); ++d)
{
B=A, A.clear();
for(int i=0,u,v; i<l; ++i)
{
u=Find(B[i].first), v=Find(B[i].second);
if(u>v) std::swap(u,v);
if(u!=v)
{
F[u]=v;
if(u>n && v>n) A.push_back(mp(u-n,v-n));
}
else --s[d];
}
}
for(int i=1; i<=m; ++i) s[i]+=s[i-1];
for(int i=1; i<=m; ++i) s[i]+=s[i-1];
for(int i=1; i<=m; ++i) Ans[i]+=1ll*(i+1)*n-1-s[i];
}
for(int i=1; i<=m; ++i) printf("%lld\n",Ans[i]); return 0;
}

J.Philosophical … Balance(后缀数组/后缀自动机 零和博弈)

https://www.bilibili.com/video/av38542305 44:28 J

\(Description\)

题面在这儿(或者去牛客 EC-Final J上看)。

简要题意:给定一个长为\(n\)的字符串,记\(s_i\)为从\(i\)开始的后缀。

两个人进行博弈,先手任意确定一个概率序列\(p_i\geq0,\sum_{i=1}^np_i=1\)。后手确定一个后缀\(j\)。先手想要最大化下式的值,后手想要最小化下式的值,两人按照最优策略决定,求最后下式的值:

\[\max_{\{p_i\}}\left(\min_{j=1}^n\left(\sum_{k=1}^np_k\mathbb{lcp}(s_k,s_j)\right)\right)
\]

多组数据,\(n\leq2\times10^5,\sum n\leq 5\times10^5\)。

\(Solution\)

一个最大化一个最小化,可以看做先手的收益是\(\sum_{k=1}^np_k\mathbb{lcp}(s_k,s_j)\),后手的收益是\(-\sum_{k=1}^np_k\mathbb{lcp}(s_k,s_j)\),所以这就是一个零和博弈,答案会在纳什均衡点处取到。即第一个人选择一种混合策略,使得自己在最坏情况下收益最大,也就是对面不管怎么决策,收益都一样。

SAM:

大概就是找出后缀树上的后缀节点,然后令它们的收益相同,从而解出各个位置的\(p\)和收益。(然而我不知道用SAM怎么标记后缀节点QAQ哪位dalao教我一下怎么用SAM写啊QAQ)

复杂度\(O(n)\)。

(随便记的忽略下面这一段吧)

\(p_1f_1=p_2f_2=p_3f_3,\quad p_1+p_2+p_3=1\)

\(pf_1=(1-p)f_2\)

求出这个的\(p\),然后令\(f'=pf_1\),求\(pf'=(1-p)f_3\),算出来的\(1-p\)就是原方程的\(p_3\)。

SA:

我们知道排名在\([l,r]\)中的后缀的LCP是\(\min\limits_{i=l+1}^r\{height_i\}\),设最小值的位置是\(p\),如果当前的两个后缀\(j,k\)在\(p\)的两边,那么此时的LCP就是\(height_p\);否则\(j,k\)在同侧,可以考虑递归到两边去做。

能够想到的是,局部最优决策下的概率比等于全局最优决策下的概率比。也就是说令左边子区间在最优决策下,各位置的概率比为\(p_1:p_2:p_3...\),那么在于右区间合并,也就是在全局中,左区间各位置的概率比仍为\(p_1:p_2:p_3...\)。

这个结论不难证明,当\(j,k\)同在左区间中的时候,答案是和右区间无关的(否则\(j,k\)有一个在右区间的话,答案和\(height_p\)有关,等会再讨论)。

所以我们可以分治去做,最后合并左右两区间,也就是\(j,k\)在\(p\)的异侧的时候。

设左区间的答案为\(L\),整体分到的概率是\(x\),右区间的答案是\(R\),整体分到的概率就是\(1-x\)。

假如后手选择\(j\)在左区间,那么\(k\)在右区间时先手的收益是\(Lx+ht_p(1-x)\);\(j\)在右区间,收益就是\(R(1-x)+ht_px\)。

先手会令这两个式子相等,所以我们能解出\(x\),然后代到左式或右式就能得到当前区间的答案了。

分治复杂度\(O(n)\),建\(SA\)的复杂度\(O(n\log n)\)。

//164ms	27744KB
#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int N=2e5+5; int Log[N];
struct Suffix_Array
{
int n,tm[N],sa[N],sa2[N],rk[N],ht[N],pos[N][18];
char s[N]; void Build()
{
scanf("%s",s+1), n=strlen(s+1);
// memset(rk,0,std::min(N,n*2)<<2);//!
// memset(sa2,0,std::min(N,n*2)<<2); int m=26,*x=rk,*y=sa2;
for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]=s[i]-'a'+1];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[i]]--]=i;
for(int p=0,k=1; k<n; k<<=1,m=p,p=0)
{
for(int i=n-k+1; i<=n; ++i) y[++p]=i;
for(int i=1; i<=n; ++i) if(sa[i]>k) y[++p]=sa[i]-k; for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[y[i]]]--]=y[i]; std::swap(x,y), x[sa[1]]=p=1;
for(int i=2; i<=n; ++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&sa[i]+k<=n&&sa[i-1]+k<=n&&y[sa[i]+k]==y[sa[i-1]+k])?p:++p;//如果不清空要这么写
// x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p:++p;
if(p>=n) break;
}
for(int i=1; i<=n; ++i) rk[sa[i]]=i;
ht[1]=0;
for(int k=0,i=1,p; i<=n; ++i)
{
if(rk[i]==1) continue;
if(k) --k;
p=sa[rk[i]-1];
while(i+k<=n && p+k<=n && s[i+k]==s[p+k]) ++k;
ht[rk[i]]=k;
}
}
inline int Min(int x,int y)
{
return ht[x]<ht[y]?x:y;
}
inline int QueryMin(int l,int r)
{
int k=Log[r-l+1];
return Min(pos[l][k],pos[r-(1<<k)+1][k]);
}
void Init_ST(const int n)
{
for(int i=1; i<=n; ++i) pos[i][0]=i;
for(int j=1; j<=Log[n]; ++j)
for(int t=1<<j-1,i=n-t; i; --i)
pos[i][j]=Min(pos[i][j-1],pos[i+t][j-1]);
}
double Solve(int l,int r)
{
if(l==r) return n-sa[l]+1;
int p=QueryMin(l+1,r);
double L=Solve(l,p-1),R=Solve(p,r),v=ht[p];
return (L*R-v*v)/(L+R-2*v);
}
void Work()
{
Build(), Init_ST(n), printf("%.11lf\n",Solve(1,n));
}
}sa; int main()
{
// freopen("bb.in","r",stdin);
// freopen("bb.out","w",stdout); for(int i=2; i<N; ++i) Log[i]=Log[i>>1]+1;
int T; scanf("%d",&T);
while(T--) sa.Work(); return 0;
}


2018 EC-Final 部分题解 (A,J)的更多相关文章

  1. 2018 IEEE极限编程大赛 题解

    去年742,今年72,也算一种小小的进步. 明年前30(笑 1. Drawing Rooted Binary Trees 给定一个树的中序和前序的遍历,要求输出这棵树(包括空格的) #include ...

  2. Tsinghua 2018 DSA PA2简要题解

    反正没时间写,先把简要题解(嘴巴A题)都给他写了记录一下. upd:任务倒是完成了,我也自闭了. CST2018 2-1 Meteorites: 乘法版的石子合并,堆 + 高精度. 写起来有点烦貌似. ...

  3. 2018年长沙理工大学程序设计竞赛 J - 杯子

    题意: 链接:https://www.nowcoder.com/acm/contest/96/J一天durong同学买了一个无限长的杯子,同时买了n个球,并且标号为1,2,3......n,duron ...

  4. 2018牛客多校2 - J farm 随机乱搞/二进制分组

    题意:给定n*m的格子,每个格子有不同的种类,q次操作,每次操作使[x1,y1]到[x2,y2]的格子除了k类型的以外都删除,最后单次询问所有格子被删了几个 官方题解提到了两种有意思的做法,随机和二进 ...

  5. 2018 Multi-University Training Contest 4 Problem J. Let Sudoku Rotate 【DFS+剪枝+矩阵旋转】

    任意门:http://acm.hdu.edu.cn/showproblem.php?pid=6341 Problem J. Let Sudoku Rotate Time Limit: 2000/100 ...

  6. Gym - 101981J The 2018 ICPC Asia Nanjing Regional Contest J.Prime Game 计数

    题面 题意:1e6的数组(1<a[i]<1e6),     mul (l,r) =l × (l+1) ×...× r,  fac(l,r) 代表 mul(l,r) 中不同素因子的个数,求s ...

  7. 2018湘潭邀请赛 AFK题解 其他待补...

    A.HDU6276:Easy h-index Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/O ...

  8. Tsinghua 2018 DSA PA3简要题解

    CST2018 3-1-1 Sum (15%) 简单的线段树,单点修改,区间求和. 很简单. CST2018 3-1-2 Max (20%) 高级的线段树. 维护区间最大和,区间和,左边最大和,右边最 ...

  9. ZOJ 4067 - Books - [贪心][2018 ACM-ICPC Asia Qingdao Regional Problem J]

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4067 题意: 给出 $n$ 本书(编号 $1 \sim n$), ...

随机推荐

  1. Linux 编程笔记(三)

    上一章节对文件的基本属性做了一个笔记,续上次笔记对Linux文件的属性和属性组做一笔记 我安装的是虚拟机操作系统的版本还KaliLinux但是系统启动速度拖延,所以刚开始还是配置Centos 1.Li ...

  2. tensorflow(4):神经网络框架总结

    #总结神经网络框架 #1,搭建网络设计结构(前向传播) 文件forward.py def forward(x,regularizer): # 输入x 和正则化权重 w= b= y= return y ...

  3. poj1155 依赖背包

    /* 依赖背包 dp[i][j]表示i结点为根的树选择j个用户时的最大剩余费用 即背包容量是j,价值是最大费用 */ #include<iostream> #include<cstr ...

  4. Python判断字符串是否xx开始或结尾

    判断是否xx开始 使用startswith 示例代码: String = "12345 上山打老虎" if str(String).startswith('1'): #判断Stri ...

  5. linux安装siege

    siege安装笔记 本文介绍centos和ubuntu安装方法 centos安装 下载: [root@ siege-4.0.4]# wget http://download.joedog.org/si ...

  6. bat 获取拖放文件路径或名称

    获取路径: @echo offset path=%~dp1echo %path%pause 获取路径及名称: @echo offset path=%~dp1%~nx1echo %path%pause

  7. gulp构建自动化项目

    'use strict'; var gulp = require('gulp'), browserSync = require('browser-sync').create(), SSI = requ ...

  8. SqlServer 分页存储过程

    SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE [dbo].[usp_CommonDataResourcePaged ...

  9. python爬虫实例

    import re import requests from bs4 import BeautifulSoup # 主方法 def main(): # 给请求指定一个请求头来模拟chrome浏览器 h ...

  10. IDEA控制台问题:java lang OutOfMemoryError:PermGen space

    PermGen space的全称是Permanent Generation space,是指内存的永久保存区域. OutOfMemoryError: PermGen space从表面上看就是内存溢出, ...