orangejuice's blog

挂了请务必叫我

ARC114 - Sequence Scores

题目大意:对于一个序列 \(A=a_i,a_i\in[1,m]\) ,定义 \(f(A)\)

对于一个全零的初始序列,每次选择一个区间对于某一个值取 \(\max\) ,最少生成 \(A\) 的步数

求所有 \(m^n\)\(A\)\(f(A)\) 之和

首先考虑 \(f(A)\) 的计算,显然可以采用如下方法

1
2
3
4
5
6
7
b[i]=0
Function Solve(l,r)
v=min a[l..r]
for i in l,r
b[i]=max{b[i],v}
Divide a[l..r] into contiguous ranges that a[i]!=b[i] , Solve(l',r')

那么考虑计算一个区间 \([l,r]\)\(\text{Solve}\) 的次数

显然区间 \([l,r]\)\(\text{Solve}\) 当且仅当

\(\min\{a_i|i\in[l,r]\}>\max(a_{l-1},a_{r+1})\)

对于不同的 \(r-l+1\) ,枚举 \(\min\) ,计算方案数即可

注意考虑 \(l=1\or r=n\) 的边界情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const int N=5010,P=998244353;

int n,m;
int Pow[N][N],F[N][3];

int main(){
n=rd(),m=rd();
rep(i,0,N-1) rep(j,*Pow[i]=1,N-1) Pow[i][j]=1ll*Pow[i][j-1]*i%P;
rep(i,1,n) rep(j,0,2) rep(k,1,m) {
F[i][j]=(F[i][j]+1ll*(Pow[m-k+1][i]-Pow[m-k][i]+P)*Pow[k-1][j])%P;
}
int ans=0;
rep(i,1,n) rep(j,i,n) {
int c=(i>1)+(j<n);
ans=(ans+1ll*F[j-i+1][c]*Pow[m][n-(j-i+1)-c])%P;
}
printf("%d\n",ans);
}

ARC114 - Moving Pieces on Line

题目大意:

白色的数轴上有 \(n\) 个球 \(a_i\) ,给定若干递增且不交的区间 \([t_i,t_{i+1})\)

每次选择一个球向左或者向右滚,且将滚过的一段反色

求最小步数恰好仅将给定区间染黑色,或者确定不存在方案

模型转化

首先显然可以发现,每个小球只会滚过一段区间一次

设小球 \(i\) 最终停在 \(b_i\) ,则滚过这段数轴会被反色,且代价为 \(|a_i-b_i|\)

将最终颜色做异或差分,那么对于目标的反色,我们认为就是在每个 \(t_i\) 处放置了一个1

而对于所有 \(a_i\) ,就是在 \(a_i,b_i\) 处分别放置了一个1,这样就完全避免了关于 \(a_i,b_i\) 大小关系的问题

计算答案

由于已经固定了 \(a_i\) (设 \(a_i\) 已经排好序),我们需要决策 \(b_i\)

那么可以预先得到哪些位置需要放置奇数个 \(b_i\) ,设这个集合为 \(pos\)

\(|pos|>n\) ,显然无解

否则, \(b_i\) 的放置仅有两种情况

1.放在某一个 \(pos_i\)

2.让两个 \(b_i\) 放在同一个位置

对于 \(a,pos\) 排序之后的情况,显然较小的 \(a_i\) 会匹配较小的 \(pos_i\) ,代价为 \(|a_i-pos_i|\)

而情况2用掉的两个 \(b_i\) ,选择使用 \(b_i,b_{i+1}\) 一定不劣,并且代价就是 \(a_{i+1}-a_i\)

那么令 \(dp_{i,j}\) 表示前 \(i\)\(a_i\) ,已经匹配了 \(j\)\(pos_j\) 的代价,如上决策即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=5010,P=998244353;

int n,m;
int a[N],b[N];
ll dp[N][N];
int h[N*2],hc;
int s[N*2],t[N*2];
int pos[N*2],c;

int main(){
n=rd(),m=rd();
rep(i,1,n) a[i]=rd(),h[++hc]=a[i];
rep(i,1,m) b[i]=rd(),h[++hc]=b[i];
sort(a+1,a+n+1);
sort(h+1,h+hc+1),hc=unique(h+1,h+hc+1)-h-1;
rep(i,1,n) {
a[i]=lower_bound(h+1,h+hc+1,a[i])-h;
s[a[i]]^=1;
}
rep(i,1,m) {
b[i]=lower_bound(h+1,h+hc+1,b[i])-h;
t[b[i]]^=1;
}
rep(i,1,hc) if(s[i]^t[i]) pos[++c]=i;
if(c>n || (n-c)&1) return puts("-1"),0;
memset(dp,63,sizeof dp),dp[0][0]=0;
rep(i,1,n) rep(j,0,min(i,c)) {
if(j<c) cmin(dp[i][j+1],dp[i-1][j]+abs(h[a[i]]-h[pos[j+1]]));
if(i<n) cmin(dp[i+1][j],dp[i-1][j]+h[a[i+1]]-h[a[i]]);
}
printf("%lld\n",dp[n][c]);
}

AtCoder Regular Contest 115 #D

Solution1

考虑用 \(\text{FWT}\) 来理解这个式子,容易发现 \(\text{FWT}\) 之后求积的式子,满足

对于任意 \((u_i,v_i)\)

如果 \(u_i,v_i\) 中有一者被选择,答案为0,否则权值 \(\times 2\)

那么显然对于一个连通块,设其大小为 \(c\) ,放在一起考虑

\(\text{FWT}\) 的式子里它们同时出现或者同时不出现

枚举最后 \(\text{FWT}\) 回来时的项与在这 \(c\) 个位置中出现 \(i\)

对于选择这个连通块的情况,贡献为 \((-1)^i\)

对于不选的情况,贡献为 \(1\)

显然只有 \(2|i\) 时贡献为2,乘上组合数完成转移,连通块之间背包合并

就能得到最终计算答案的项中出现了几个1,然后与 \(\text{FWT}\) 的系数合并即可

\[ \ \]

Solution2

对于一个连通块,考虑取出一个生成树

容易发现,仅使用这个生成树上的边,就能构成任何一个包含 \(2k\) 个奇点的情况

对于多余的边,类似异或线性基,它们都是可选可不选的

于是直接统计答案即可

Sol1 和 Sol2 的式子是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const int N=5010,P=998244353;
int n,m,dp[N];
ll qpow(ll x,ll k=P-2){
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}
vector <int> G[N];
int c,vis[N],C[N][N];
void dfs(int u){
if(vis[u]) return;
vis[u]=1,c++;
for(int v:G[u]) dfs(v);
}

int main(){
n=rd(),m=rd();
rep(i,0,n) rep(j,*C[i]=1,i) C[i][j]=C[i-1][j]+C[i-1][j-1],Mod1(C[i][j]);
rep(i,1,m) {
int u=rd(),v=rd();
G[u].pb(v),G[v].pb(u);
}
dp[0]=1;
rep(u,1,n) if(!vis[u]) {
c=0,dfs(u);
drep(i,n,0) {
int s=0;
rep(j,0,min(i,c)) if(~j&1) {
s=(s+2ll*dp[i-j]*C[c][j])%P;
}
dp[i]=s;
}
}
int d=qpow(2,P-1-n+m);
rep(i,0,n) printf("%d\n",int(1ll*dp[i]*d%P));
}

ARC117 - Gateau

题目大意:给定一个长度为 \(2n\) 的非负环序列 \(x_0,x_1,\cdots x_{2n-1}\) ,以及 \(2n\) 条限制,每条都是

\(\forall A_i,\sum_{j=0}^{n-1} x_{i+j\mod 2n}\ge A_i\)

求最小化 \(\sum x_i\)


\[ \ \]

转化为前缀和作差之后,令人联想到差分约束,但是难以处理跨过环末的限制

于是二分答案 \(s=x_{2n-1}\) ,建立最长路图

\(\forall i<n,dis_{i+n}\ge dis_{i}+A_i\)

\(\forall i\ge n,dis_{i-n}\ge dis_{i}+A_i-s\)

\(\forall i<2n-1,dis_{i+1}\leq dis_i\)

那么无解的条件就是:存在正环或者求得 \(dis_{2n-1}>s\)

自然无法直接通过 \(\text{SPFA}\) 来跑。。。

考虑所有的边构成了一条 \(0-2n-1\) 的零链 和若干极小的二元环

如果二元环出现正环则无解,否则任意一条最长路路径总是可以描述为

\(i<j<n , i(+n)\rightarrow j(+n)\)

在中间点 \(k\) 可以选择花费0的代价向后走,或者

\(k<n:k\rightarrow k+n,cost=A_k\)

\(k\ge n:k\rightarrow k-n,cost=A_k-s\)

也就是在 \(k,k+n\) 之间反复横跳,由此发现一条路径就是

\(0-n-1\) 进行扫描,并且允许中间 \(\pm n\) 横跳

(当然这里漏掉了一个特殊边,即 \(dis_{n}\ge dis_{n-1}\) ,这是构成环的边)

这样写出一个变种的 \(\text{Bellman Ford}\) ,由于图的特殊性,只需要常数轮即可确定正环

具体的,当图上不存在正环时,扫描最多经过一次环就会停止更新

也就是这样横跳的扫描更新只会进行常数轮(2轮?)

如果若干轮后依然在更新,说明出现了正环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
int cmax(int &a,int b){ return a<b?a=b,1:0; }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=3e5+10,INF=1e9+10;

int n;
int A[N],dp[N];

int Check(int s) {
rep(i,0,n-1) if(A[i]+A[i+n]-s>0) return 0;
rep(i,0,n*2-1) dp[i]=0;
dp[n*2-1]=s;
int f=0;
rep(k,0,5) {
f=0;
rep(i,0,n-1) {
f|=cmax(dp[i+n],dp[i]+A[i]);
f|=cmax(dp[i],dp[i+n]+A[i+n]-s);
if(i<n-1) {
f|=cmax(dp[i+1],dp[i]);
f|=cmax(dp[i+n+1],dp[i+n]);
}
}
f|=cmax(dp[n],dp[n-1]);
}
if(f || dp[n*2-1]>s) return 0;
return 1;
}

int main() {
n=rd();
rep(i,0,n*2-1) A[i]=rd();
int res=-1;
for(int l=0,r=1.05e9,mid;l<=r;) Check(mid=(l+r)>>1)?r=mid-1,res=mid:l=mid+1;
printf("%d\n",res);
}

ARC 117 - Miracle Tree

话说我只能蒙结论。。。

打表或者理性分析可以发现一些性质

  1. \(\nexists E_i=E_j\)

2.如果确定 \(E_i\) 从小到大的顺序 \(P_i\) ,就能确定一组最优的 \(E_i\)

(但是对于平凡的 \(P_i\) ,这个过程会极其恶心,因此考虑特殊化 \(P_i\)

3.设 \(\displaystyle f(P)=\sum_{i=2}^n dis(P_{i-1},P_i)\) ,即遍历排列的距离和

\(\max\{E_i\}\ge \min\{f(P)\}+1\)

显然

由此确定了一个下界,接下来将说明可以取到下界

1.对于一个排列 \(P_{i}\) ,如果 \(P_i\) 是一组 \(\text{dfs}\) 序,那么满足 \(\max\{E_i\}=f(P)+1\) (容易模拟发现)

  1. \(\min\{f(P)\}\)\((P_1,P_n)\) 恰好为一条直径时取到,显然存在这样一组 \(\text{dfs}\) 序满足要求

由此确定了答案 \(P\) 可以是任何一组以某一条直径两个端点为 \(P_1,P_n\)\(\text{dfs}\)

容易给出一个合法解,代码实现极为暴力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const int N=2e5+10,INF=1e9+10;

int n;
vector <int> G[N];
int F[N][20],D[N],E[N];
int ma=-1,id;
void dfs(int u,int f) {
if(D[u]>ma) ma=D[u],id=u;
F[u][0]=f,E[u]=D[u];
rep(i,1,18) F[u][i]=F[F[u][i-1]][i-1];
for(int v:G[u]) if(v!=f) D[v]=D[u]+1,dfs(v,u),cmax(E[u],E[v]);
sort(G[u].begin(),G[u].end(),[&](int x,int y){ return E[x]<E[y]; });
}
int LCA(int x,int y){
if(D[x]<D[y]) swap(x,y);
for(int del=D[x]-D[y],i=0;(1<<i)<=del;++i) if(del&(1<<i)) x=F[x][i];
if(x==y) return x;
drep(i,18,0) if(F[x][i]!=F[y][i]) x=F[x][i],y=F[y][i];
return F[x][0];
}
int Dis(int x,int y){ return D[x]+D[y]-2*D[LCA(x,y)]; }

int lst;
ll A[N];
void dfs2(int u,int f) {
if(!lst) A[lst=u]=1;
else A[u]=A[lst]+Dis(lst,u),lst=u;
for(int v:G[u]) if(v!=f) dfs2(v,u);
}

int main(){
n=rd();
rep(i,2,n) {
int u=rd(),v=rd();
G[u].pb(v),G[v].pb(u);
}
dfs(1,0);
int u=id;
dfs(u,0),dfs2(u,0);
rep(i,1,n) printf("%lld ",A[i]);
}

ARC117 - Zero-Sum Ranges 2

题目大意:计算由 \(n\)\(+1\)\(n\)\(-1\) 构成的序列,且 包含恰好 \(k\) 个和为零的区间 的数量

显然需要转化为前缀和,通过前缀和相等的二元组数确定和为0的数量

而恰好 \(n\)\(+1,-1\) 可以转化为 \(s_{2n}=0\)

\(m=2n+1\) ,接下来我们要计算 \(m\) 个元素,且 \(s_1=s_m=0,s_{i}=s_{i-1}\pm 1\) 的序列

\(s_i\) 的变化是连续的,考虑分为 \(s_i\ge 0,s_i<0\) 的两部分

\(\ge 0\) 为例,从高到低确定每个连续的峰折线的情况,折线组的位置不重要,只需要知道个数

\(dp_{i,j,c}\) 表示当前 \(i\) 个元素确定,且已经确定的元素分成了 \(j\) 段,得到 \(c\) 个相同对的方案数

每个段中可能包含折线组,且两端一定是当前的最低值,状态数为 \(O(n^4)\)

每次 \(dp\) 在当前状态上扩展下一层的情况,由于变化连续,得到新的状态

1.每个段两边应该出现新的位置

2.两个段向两边扩展时,可能共用一个位置

3.可能出现新的峰顶

根据2,3的情况,组合数转移

如果直接枚举2,3情况,复杂度为 \(O(n^6)\)

实际上容易发现2,3情况可以放在一起处理

具体的,对于新出现的 \(j+1\) 个位置(也就是每两个段之间的间隔)是一定会出现的,用这 \(j+1\) 个可以合并为一整个段

剩余的情况,额外插入一个元素,就是在 \(j+1\) 个位置中分配,且每额外加入一个就能额外产生一个新的段

复杂度为 \(O(n^5)\)

最终合并 \(s_i\ge 0,s_i<0\) 的两部分,由于 \(s_1=s_m=0\) ,所以开头结尾两端必须是0,然后两部分的段交替排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
enum{N=62};
int n,m,k;
ll C[N][N],dp[N][N][910];
// dp[i][j][s]
// i places taken
// j elements
// s ranges generated
int D2(int n){ return n*(n-1)/2; }
int main() {
scanf("%d%d",&n,&k),m=n*2+1;
rep(i,0,m) rep(j,*C[i]=1,i) C[i][j]=C[i-1][j]+C[i-1][j-1];
rep(i,0,m) if(D2(i)<=k) dp[i][i][D2(i)]=1;
rep(i,1,m) rep(j,1,i) rep(s,0,k) if(dp[i][j][s]) {
rep(d,j+1,m-i) {
if(s+D2(d)>k) break;
dp[i+d][d-j][s+D2(d)]+=dp[i][j][s]*C[d-1][j];
}
}
ll ans=0;
rep(i,1,m) rep(j,1,i) rep(s,0,k) if(dp[i][j][s]) {
ans+=dp[i][j][s]*dp[m-i][j-1][k-s];
}
printf("%lld\n",ans);
}

COCI20102011 Contest#Final D (dp)

我们将一个操作序列看做由左右括号,空格构成的字符串,则序列大致长这个样子

\(\text{_ ( ( ) _ ( ) ) ( _ ( ( ) ) ( }\)

很显然,一个失配的左括号只能在最外层出现,而空格可以出现在任意位置

dp一个括号序列让人想到区间dp,但是这个题目的区间实际只需要用长度就可以描述

\(dp[t][l][r][f1][f2]\) 表示用 \(t\) 的时间从 \(l\) 走到 \(r\)\(f1\) 表示是不是最外层括号, \(f2\) 表示当前 \(dp\) 是否受到单纯括号序列的限制

其中,引入的单纯括号序列是为了防止出现重复转移,其意思就是这个括号序列两端必须是一对匹配的左右括号,而中间随意

转移大致如下:

1.那么对于非单纯的括号序列,可以在序列插入空格或者失配的左括号(需要满足 \(f1\) ),从 \(dp[t-1]\) 转移过来

2.对于任何的括号序列,都可以在两端找到匹配的的左右括号,从 \(dp[t-2]\) 转移过来,且完成匹配后 \(f1\) 应为 \(0\)

3.且一个非单纯的括号序列是可以分割的,为了不重复,强制分割的左序列是单纯的即可

实际会发现,一个单纯的括号序列可以认为一定不是最外层括号,即 \(f2\) 为真时, \(f1\) 一定为假,所以可以压缩为三种状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define reg register
#define rep(i,a,b) for(reg int i=a;i<=b;++i)
#define drep(i,a,b) for(reg int i=a;i>=b;--i)
char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=51,P=10007;

int n,m,T;
int E[N][N];
int dp[N][N][N][2][2];

int main(){
n=rd(),m=rd(),T=rd();
memset(E,-63,sizeof E);
rep(i,1,m) {
int u=rd(),v=rd(),c=IO==' '?getchar():IO;
if(c>='A' && c<='Z') E[u][v]=c-'A'+1;
else if(c>='a' && c<='z') E[u][v]='a'-c-1;
else E[u][v]=0;
}
rep(i,1,n) rep(j,0,1) dp[0][i][i][j][0]=1;
rep(k,1,T){
rep(l,1,n) rep(r,(k<T?1:n),n) rep(fl,0,1) rep(fl2,0,1) {
ll res=0;
if(!fl2) {
rep(i,2,k-1) rep(j,1,n) res+=dp[i][l][j][0][1]*dp[k-i][j][r][fl][0];
rep(i,1,n) if(E[l][i]>=0 && (!E[l][i] || fl)) res+=dp[k-1][i][r][fl][fl2];
}
if(k>1) rep(i,1,n)
if(E[l][i]>0) rep(j,1,n)
if(E[j][r]+E[l][i]==0) res+=dp[k-2][i][j][0][0];
dp[k][l][r][fl][fl2]=res%P;
}
}
int ans=0;
rep(i,1,T) ans+=dp[i][1][n][1][0];
printf("%d\n",ans%P);
}



COCI20122013 Contest#5 F

不知道题解在写什么.jpg

Part1 : Naive的dp

\(dp_{i,a,b,j}\) 表示当前时刻 \(i\) ,两队比分为 \(a,b\) ,球在 \(j\) 手上的概率

转移非常简单就不说了,单次转移为 \(O(n)\) ,复杂度为 \(O(n^2r^2T)\)

在优秀卡常+O2下跑进700ms

优化的话: 1.float

2.分小块加速

3.循环展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include<bits/stdc++.h>
using namespace std;
#pragma GCC optimize(2)
typedef float db;
#define reg register
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,const T &b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int INF=1e9+10;
const db eps=1e-12;

int n,R,T;
db dp[510][11][11][210],p[410],sz[410],ans[11][11];
struct Edge{
int to,nxt;
} e[80000];
int head[410],ecnt;
void AddEdge(int u,int v) {
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}
int E[410][410],G[410][410];
const int D=5;
db tmp[210][1<<D];

int main(){
n=rd(),R=rd(),T=rd();
int m=(n*2+D-1)/D;
rep(i,1,n*2) {
scanf("%f",&p[i]);
int e=rd(),f=rd();
sz[i]=e+f+1;
rep(j,1,e) {
int x=rd();
if(i>n) x+=n;
E[i][x]=1;
}
rep(j,1,f) {
int x=rd();
if(i<=n) x+=n;
E[i][x]=1;
}
rep(j,1,m) {
int f=(j-1)*D+1;
rep(k,0,D-1) G[i][j]|=E[i][f+k]<<k;
if(G[i][j]) AddEdge(i,j);
}
}
dp[0][0][0][1]=1;
for(reg int i=0;i<=T;++i) {
for(reg int a=0;a<=R;++a) {
for(reg int b=0;b<=R;++b) {
for(reg int j=1;j<=n*2;++j) if(dp[i][a][b][j]>eps) {
if(a==R || b==R || i==T) { ans[a][b]+=dp[i][a][b][j]; continue; }
db t=dp[i][a][b][j]/sz[j];
for(reg int k=1;k<=m;k+=4) {
tmp[k][G[j][k]]+=t;
tmp[k+1][G[j][k+1]]+=t;
tmp[k+2][G[j][k+2]]+=t;
tmp[k+3][G[j][k+3]]+=t;
}
if(j<=n) {
dp[i+1][a][b][n+1]+=t*(1-p[j]);
dp[i+1][a+1][b][n+1]+=t*p[j];
} else {
dp[i+1][a][b][1]+=t*(1-p[j]);
dp[i+1][a][b+1][1]+=t*p[j];
}
}
for(reg int j=1;j<=m;++j) {
int f=(j-1)*D+1;
rep(k,1,(1<<D)-1) {
(k&1) && (dp[i+1][a][b][f]+=tmp[j][k]);
(k&2) && (dp[i+1][a][b][f+1]+=tmp[j][k]);
(k&4) && (dp[i+1][a][b][f+2]+=tmp[j][k]);
(k&8) && (dp[i+1][a][b][f+3]+=tmp[j][k]);
(k&16) && (dp[i+1][a][b][f+4]+=tmp[j][k]);
tmp[j][k]=0;
}
}
}
}
}
rep(i,0,R) rep(j,0,R) if(i!=R || j!=R) printf("%.10f\n",ans[i][j]);
}

\[\ \]

Part2: 状态割裂

定义每个球进的时间为关键点,我们发现关键点的状态非常单一,只有两种

一个合法的转移序列可以被分为若干关键点的段以及最后一段到达 \(T\) 之后停止转移

考虑预处理两个关键点之间的转移概率

\(g_{a,b,i}\) 为当球在 \(a\) 队一号队员时, \(i\) 次后 \(b\) 队进球的概率

可以枚举 \(a\) ,类似上面的 \(dp\) ,去掉比分的一维即可

预处理复杂度为 \(O(Tn^2)\)

然后 \(dp\) 时直接枚举两个关键点转移,令

\(h_{i,a,b,j}\) 时刻 \(i\) 比分为 \(a,b\) ,球在 \(j\) 队一号队员手上的概率

转移分两种

1.枚举下一个在 \(T\) 以内的关键点转移

复杂度为 \(O(T)\)

2.考虑在 \(T\) 以内的时间不再出现进球了

需要预处理出当球在 \(i\) 队手上时, \(j\) 次内出现进球的概率 \(s_{i,j}\) ,这个直接由 \(g\) 数组累前缀和即可

\(dp\) 关键点的复杂度为 \(O(T^2r^2)\)

大概比上面代码快4-5倍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<bits/stdc++.h>
using namespace std;
#pragma GCC optimize(2)
typedef float db;
#define reg register
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int INF=1e9+10;
const db eps=1e-12;

int n,R,T;
struct Edge{
int to,nxt;
} e[80000];
int head[410],ecnt;
void AddEdge(int u,int v) {
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}
db p[410],sz[410],ans[11][11],f[510][210],g[2][2][510],s[2][510];
// g[i][j][k] 在i拿球的情况下,j在第k次进球了
db h[510][11][11][2];

int main(){
n=rd(),R=rd(),T=rd();
rep(i,1,n*2) {
scanf("%f",&p[i]);
int e=rd(),f=rd();
sz[i]=e+f+1;
rep(j,1,e) {
int x=rd();
if(i>n) x+=n;
AddEdge(i,x);
}
rep(j,1,f) {
int x=rd();
if(i<=n) x+=n;
AddEdge(i,x);
}
}
rep(d,0,1) {
f[0][d*n+1]=1;
rep(i,0,T) {
rep(j,1,n*2) if(f[i][j]>eps) {
db t=f[i][j]/sz[j];
for(reg int k=head[j];k;k=e[k].nxt) f[i+1][e[k].to]+=t;
f[i+1][j>n?1:n+1]+=t*(1-p[j]);
g[d][j>n][i+1]+=t*p[j];
f[i][j]=0;
}
}
rep(i,0,T) {
s[d][i]=g[d][0][i]+g[d][1][i];
if(i) s[d][i]+=s[d][i-1];
}
}
h[0][0][0][0]=1;
rep(i,0,T) {
rep(a,0,R) rep(b,0,R) rep(j,0,1) if(h[i][a][b][j]>eps) {
if(a==R || b==R || i==T){ ans[a][b]+=h[i][a][b][j]; continue; }
rep(k,1,T-i) {
// 能在结束前产生一次进球
h[i+k][a+1][b][1]+=h[i][a][b][j]*g[j][0][k];
h[i+k][a][b+1][0]+=h[i][a][b][j]*g[j][1][k];
}
ans[a][b]+=h[i][a][b][j]*(1-s[j][T-i]);
}
}
rep(i,0,R) rep(j,0,R) if(i<R || j<R) printf("%.10f\n",ans[i][j]);
}



COCI2013-2014 Contest#1 F SLASTIČAR

其实挺妙的一个数据结构题

题意: 给定一个A串,对于查询的每个 \(B\) 串,从头开始匹配匹配 \(A\) 的每个后缀,每次匹配失败的代价是 \(\text{LCP}+1\) 可,匹配成功的代价是 \(|B|\) ,且立即停止,求代价总和

\(A\) 串长为 \(n\) ,查询个数为 \(q\) ,查询总长为 \(m\)

我们知道求两个串的 \(\text{LCP}\) 可以把两个串中间放一个符号分开,跑后缀数组/后缀自动机

但是首先是 \(m=3\cdot 10^6\) ,内存就开不下

而且发现,如果 \(|B|\) 的某个前缀未出现在 \(A\) 中,后面的部分都不造成贡献,所以可以在 \(A\) 串中定位 \(B\) 的每个前缀

这样就只需要求 \(A\) 的后缀数组即可,然而,这个并不好实现

假设当前已经定位了一个长度为 \(i\) 的前缀,其对应的合法后缀排名范围为 \([L_i,R_i]\)

那么接下来就是在当前前缀上接上下一个字符 \(c\) ,这个如果再外加数据结构并不好实现

考虑后缀数组的性质, \([L_i,R_i]\) 这些后缀前 \(d\) 个字符相同, \(d+1\) 个字符呈单调非递减

所以字符 \(c\) 出现的范围也一定是一段区间,可以直接两次二分得到

这样查询 \([L_i,R_i]\) 的复杂度为 \(\log n\) ,这样就用 \(O(m\log n)\) 的复杂度完成了串定位

如果不考虑每次完成匹配后停止,其实答案就是 \(\sum R_i-L_i+1\) ,即 \(\sum_i LCP_i=\sum_i \sum_j [LCP_j\ge i]\)

设最终的匹配位置为 \(p\) ,这个位置可以用线段树在最后的一段 \([L_{|B|},R_{|B|}]\) 中求最小值得到

考虑减掉多余的部分,即减去实际位置 \(>p\) 的且在后缀数组排名在 \([L_i,R_i]\) 中的部分

可以把所有的 \([L_i,R_i]\) 拿出来作为,离线询问,用树状数组维护查询,复杂度为 \(O((n+m)\log n)\)

因此总复杂度为 \(O((n+m)\log n)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair <int,int> Pii;
#define reg register
#define pb push_back
#define mp make_pair
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,const T &b){ ((a<b)&&(a=b)); }

const int N=1e5+10,M=3e6+10;

int n,m;
char s[N],t[N];

int rk[N<<1],tmp[N],cnt[N],sa[N];

void Build(int n){
rep(i,1,n) cnt[int(s[i])]++;
rep(i,1,200) cnt[i]+=cnt[i-1];
rep(i,1,n) rk[i]=cnt[(int)s[i]],sa[i]=i;
for(reg int k=1;k<n;k<<=1) {
for(reg int i=0;i<=n;++i) cnt[i]=0;
for(reg int i=1;i<=n;++i) cnt[rk[i+k]]++;
for(reg int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
for(reg int i=n;i>=1;--i) tmp[cnt[rk[i+k]]--]=i;

for(reg int i=0;i<=n;++i) cnt[i]=0;
for(reg int i=1;i<=n;++i) cnt[rk[i]]++;
for(reg int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
for(reg int i=n;i>=1;--i) sa[cnt[rk[tmp[i]]]--]=tmp[i];

for(reg int i=1;i<=n;++i) tmp[sa[i]]=tmp[sa[i-1]]+(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+k]!=rk[sa[i-1]+k]);
for(reg int i=1;i<=n;++i) rk[i]=tmp[i];
}
}

void FindChar(int &l,int &r,int len,int c){
int tl=l,tr=r;
int lres=-1,rres=-1;
while(l<=r){
int mid=(l+r)>>1;
if(s[sa[mid]+len]<=c) l=mid+1,lres=mid;
else r=mid-1;
}
if(lres==-1 || s[sa[lres]+len]!=c) { l=0; return; }
l=tl,r=tr;
while(l<=r){
int mid=(l+r)>>1;
if(s[sa[mid]+len]>=c) r=mid-1,rres=mid;
else l=mid+1;
}
l=rres,r=lres;
}


ll ans[N];
int ql[M],qr[M],qid[M],qnxt[M];
int head[N],qc;
int L[N],R[N];

struct BIT{
int s[N];
void Add(int p) { while(p<=n) s[p]++,p+=p&-p; }
int Que(int p) {
int res=0;
while(p) res+=s[p],p-=p&-p;
return res;
}
int Que(int l,int r){ return Que(r)-Que(l-1); }
} T;
struct Tree{
int s[N<<2];
void Build(int p,int l,int r){
if(l==r) { s[p]=sa[l]; return ;}
int mid=(l+r)>>1;
Build(p<<1,l,mid),Build(p<<1|1,mid+1,r);
s[p]=min(s[p<<1],s[p<<1|1]);
}
int Que(int p,int l,int r,int ql,int qr){
if(ql<=l && r<=qr) return s[p];
int mid=(l+r)>>1,res=1e9;
if(ql<=mid) cmin(res,Que(p<<1,l,mid,ql,qr));
if(qr>mid) cmin(res,Que(p<<1|1,mid+1,r,ql,qr));
return res;
}
}SGT;

int main(){
scanf("%d%s",&n,s+1);
Build(n),scanf("%d",&m);
SGT.Build(1,1,n);
rep(i,1,m) {
scanf("%s",t+1);
L[0]=1,R[0]=n;
int len=0,pos=1;
for(int j=1;t[j];++j) {
L[len=j]=L[j-1],R[j]=R[j-1];
FindChar(L[j],R[j],j-1,t[j]);
if(!L[j]){ pos=0; break; }
ans[i]+=R[j]-L[j]+1;
}
if(pos && (pos=SGT.Que(1,1,n,L[len],R[len]))<n) {
ans[i]+=pos-1,pos++;
rep(j,1,len){
qc++,ql[qc]=L[j],qr[qc]=R[j],qid[qc]=i,qnxt[qc]=head[pos];
head[pos]=qc;
}
} else ans[i]+=n;
}
drep(i,n,1){
T.Add(rk[i]);
for(int j=head[i];j;j=qnxt[j]) ans[qid[j]]-=T.Que(ql[j],qr[j]);
}
rep(i,1,m) printf("%lld\n",ans[i]);
}

COCI2013-2014 Contest#3 F 单调栈

考虑每个小区间 \([x_i,x_{i+1}]\) 中,外部光照进来只能是这样

Light1.png

其中两边是光源,红色表示被照到了

那么所有合法的被照到的段可以表示为 \([x_i,x_{i+1}]\) 中的两个小段,即

1.左边照进来的 \([s0_i,x_{i+1}]\)

2.右边照进来的 \([x_i,s1_i]\)

考虑从左到右维护可能照到右边的较优光源

显然对于每一个光源都有一个当前能遮住它最多的城市,构成(光源,限制城市)这样的点对

对于每一个较优的这样的点对 可以维护一个单调栈,维护出来应该是这样的

Light2.png

有几个较为显然的性质:

1.所有点对的 \(h_i\) 递减,否则要么是上一个光源被完全遮住了,要么是这个点可以遮住上一个光源更多

2.靠右的点对能够覆盖的范围较大,即可行区间的左端点较小

否则它此时不会产生贡献,而当后面较高的点进来时,相对更矮的它也更容易被遮住

实际上,这样的形状就会构成一个类似上凸包的东西,但是凸包上只有一部分点产生贡献

\[ \ \]

考虑依次加入点 \((x_i,h_i)\) ,显然可以弹掉 \(h_j<h_i\) 的所有点对(为了防止下面计算左端点时出现问题)

接下来的情况,类似维护凸包,但是要考虑更新限制城市 或者 弹掉点对

\[ \ \]

对于向左的情况,反着求一遍即可,最后可以减去两种区间都覆盖不到的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>
using namespace std;
typedef double db;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=3e5+10;

int n,D;
struct City{ int k,x,h; } C[N];

struct Lightpair{
//描述一个灯塔和右边遮住它最多的城市
int x,y;
Lightpair(const int &_x=0,const int &_y=0) : x(_x),y(_y) { }
db Left(){
return C[y].x+1.0*(C[y].x-C[x].x)/(C[x].h-C[y].h)*C[y].h;
}
// z这个发光对能照到的范围为[Left(),+oo)
} stk[N];
int top;
void Cover(Lightpair &x,int y){ if(Lightpair(x.x,y).Left()>x.Left()) x.y=y; }
db s[2][N];
// 将所有可能的发光区间描述为 [C[i].x,s[k][i]]


int main(){
n=rd(),D=rd();
rep(i,1,n) C[i].k=rd(),C[i].x=rd(),C[i].h=rd();
C[n+1].x=D;
rep(k,0,1) {
rep(i,0,n) {
while(top && C[stk[top].x].h<=C[i].h) top--; // 完全被遮住的,先弹掉
if(top) {
Cover(stk[top],i);
while(top>1){
Cover(stk[top-1],i);
if(stk[top-1].Left()>stk[top].Left()) break;
top--;
}
}
if(C[i].k) stk[++top]=i,s[k][i]=C[i].x; // 一定覆盖C[i].x,C[i+1].x
else if(top) s[k][i]=min(stk[top].Left(),(db)C[i+1].x); // 尝试让当前最优的点对覆盖过来
else s[k][i]=C[i+1].x; // 无覆盖
}
if(!k) {
reverse(C+1,C+n+1),top=0;
rep(i,1,n) C[i].x=D-C[i].x;
} else {
reverse(s[k],s[k]+n+1);
rep(i,0,n) s[k][i]=D-s[k][i];
}
}
db ans=D; // 减去不合法的
rep(i,0,n) if(s[0][i]>s[1][i]) ans-=s[0][i]-s[1][i];
printf("%.3lf\n",ans);
}

0%