orangejuice's blog

挂了请务必叫我

「USACO 2021.1 Platinum」Paint by Letters

统计连通块问题,暴力是 \(O(qn^2)\) ,而且常数大

容易想到 平面图的欧拉定理 优化

下文和代码中, \(V,E,F\) 分别为点集,边集,区域集合

其中 \(|V|\) 可以直接得到, \(|E|\) 可以 \(O(n^2)\) 前缀和预处理出来, \(O(1)\) 查询

下面处理区域个数

Solution1

前缀和所有大小为1(即被四个点包住的)区域,暴力预处理所有大小 \(>1\) 的区域,个数为 \(O(\frac{nm}{4})\)

然后可以转化为一个对于给定矩形查询包含的 矩形个数 的问题

实际上这个题目 \(q\) 小直接枚举就是了。。

复杂度为 \(O(nm+q\frac{nm}{4})\) ,足够通过时限

写的够丑可以得到这个代码

矩形区间查询问题 ,不知道有没有什么更好的方法

ps:垃圾数据没有卡,因此实际上数据中的空白块数量非常少,预处理写得稍微好一点可能还比下面的做法常数小

\[ \ \]

Solution2

\((x,y)\) 表示 \((x,y),(x+1,y),(x,y+1),(x+1,y+1)\) 中间的一个空白区域

这些空白块会被染色块之间的边隔开,但是依然可以形成四联通块

预处理出所有空白区域的连通块,每个连通块选取一个代表点 \(S_i\)

我们要统计一个区域中的空白连通块个数,注意到

跨出区域范围的空白点,并不是断开了,而是和最外层的无穷空白区合并在一起

因此可以先求出在区域中存在的连通块个数,然后将连通到区域外的部分去掉

具体实现上:

前缀和预处理出 \(S_i\) 的位置,每次查询区域中的 \(S_i\) 个数(这样的统计不完全)

然后将 \(S_i\) 在区域中,且跨出区域的白色连通块删掉即可

跨出部分枚举四条边界即可

每次查询枚举边界,因此复杂度为 \(O(nm+q(n+m))\)

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
#include<bits/stdc++.h>
using namespace std;
#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)

const int N=1e3+10,INF=1e9+10;
const int dx[]={0,0,1,-1},dy[]={1,-1,0,0};

int n,m,q;
char A[N][N];
int vis[N*N],I[N][N],E[4][N][N],S[N][N];
// B,C预处理上/左边个数
int B[N][N],C[N][N];
int X[N*N],Y[N*N],cnt;
// 搜索空白连通块
void dfs(int x,int y){
I[x][y]=cnt;
rep(i,0,3) if(E[x][y][i]) {
int x1=x+dx[i],y1=y+dy[i];
if(!x1 || !y1||x1>=n || y1>=m || I[x1][y1]) continue;
dfs(x1,y1);
}
}
int Sum(const int A[N][N],int x1,int y1,int x2,int y2){
x1--,y1--;
return A[x2][y2]-A[x1][y2]-A[x2][y1]+A[x1][y1];
}

int main(){
scanf("%d%d%d",&n,&m,&q);
rep(i,1,n) scanf("%s",A[i]+1);
rep(i,1,n) rep(j,1,m) {
E[0][i][j-1]=E[1][i][j]=A[i][j]!=A[i+1][j];
E[2][i-1][j]=E[3][i][j]=A[i][j]!=A[i][j+1];
// 处理一下空白点之间的边
if(A[i-1][j]==A[i][j]) B[i][j]++;
if(A[i][j]==A[i][j-1]) C[i][j]++;
}
// 预处理空白点之间的集合
rep(i,1,n-1) rep(j,1,m-1) if(!I[i][j]) {
X[++cnt]=i,Y[cnt]=j,vis[cnt]=q+1;
dfs(i,j),S[i][j]++;
}
rep(i,1,n) rep(j,1,m) {
B[i][j]+=B[i-1][j]+B[i][j-1]-B[i-1][j-1];
C[i][j]+=C[i-1][j]+C[i][j-1]-C[i-1][j-1];
S[i][j]+=S[i-1][j]+S[i][j-1]-S[i-1][j-1];
}
while(q--) {
int lx,ly,rx,ry; scanf("%d%d%d%d",&lx,&ly,&rx,&ry);
int V=(rx-lx+1)*(ry-ly+1);
int E=Sum(B,lx+1,ly,rx,ry)+Sum(C,lx,ly+1,rx,ry);
int F=Sum(S,lx,ly,rx-1,ry-1);
auto Check=[&](int i){ if(vis[i]!=q && lx<=X[i] && X[i]<rx && ly<=Y[i] && Y[i]<ry) vis[i]=q,F--; };
rep(i,lx,rx-1) {
if(::E[0][i][ry-1]) Check(I[i][ry-1]);
if(::E[1][i][ly]) Check(I[i][ly]);
}
rep(i,ly,ry-1) {
if(::E[2][rx-1][i]) Check(I[rx-1][i]);
if(::E[3][lx][i]) Check(I[lx][i]);
}
printf("%d\n",V-E+F);
}
}

「USACO 2021.1 Platinum」Sum of Distances

设在 \(G_i\)\(j_i\) 点可行的距离集合为 \(D_{j_i}\)

注意到一个点的 \((j_1,j_2,\ldots,j_k)\)\(dis\) 可以用如下方式确定

\(\displaystyle dis(j_1,j_2,\ldots,j_k)=\min\{\bigcap D_{j_i}\}\)

\(D_{j_i}\) 有一个简单的描述方法:

求出奇数和偶数的最小值,然后再最小值往上+ \(2k\) 的部分均存在(不停来回)

这样我们用两个值 \(odd_{j_i},even_{j_i}\) 描述了 \(D_{j_i}\)

同时也容易得到 \(\displaystyle dis(j_1,j_2,\ldots,j_k)=\min\{\max\{odd_{j_i}\},\max\{even_{j_i}\}\}\)

也就是说要在奇偶的 \(\max\) 之间取 \(\min\)

考虑 \(odd_{j_i},even_{j_i}\)\(O(N_i)\) 级别的,可以暴力合并,但是无法处理外层的 \(\min\) ,因此考虑用一个简单的 \(\text{minmax}\) 容斥解决

\(\min\{a,b\}=a+b-\max\{a,b\}\)

这样就只有 \(\max\) 要计算了,用简单的前缀和优化取 \(\max\) 操作

注意要按照 \(N_i\) 排序之后依次合并每一个 \(G_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
68
69
70
71
72
73
74
75
76
77
78
79
#include<bits/stdc++.h>
using namespace std;
typedef pair <int,int> Pii;
#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 cmax(T &a,T b){ ((a<b)&&(a=b)); }

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=2e5+10,P=1e9+7;

int n;
int C[N],R[N],D[N][2],I[N],M[N],S[N*2];
vector <int> G[N];
int dis[N][2],U,F[N],H[N];

int Solve(int S){
F[U=0]=1;
rep(k,1,n) {
int i=I[k];
rep(j,U+1,M[i]) F[j]=F[j-1];
cmax(U,M[i]);
rep(j,0,U) H[j]=0;
rep(j,R[i-1]+1,R[i]) {
int d=-1;
if(S&1) cmax(d,D[j][0]);
if(S&2) cmax(d,D[j][1]);
if(d==P) continue;
H[d]++;
}
rep(j,1,U) H[j]+=H[j-1];
rep(j,0,U) F[j]=1ll*F[j]*H[j]%P;
}
drep(j,U,1) F[j]-=F[j-1],Mod2(F[j]);
int ans=0;
rep(j,0,U) ans=(ans+1ll*j*F[j])%P;
return ans;
}

int main(){
n=rd();
rep(i,1,n) {
I[i]=i,C[i]=rd(),R[i]=R[i-1]+C[i];
rep(j,1,C[i]) G[j].clear();
rep(j,1,rd()){
int u=rd(),v=rd();
G[u].pb(v),G[v].pb(u);
}
rep(j,1,C[i]) dis[j][0]=dis[j][1]=P;
dis[1][0]=0;
static queue <Pii> que; que.push(mp(1,0));
while(!que.empty()){
int u=que.front().first,d=que.front().second; que.pop();
cmax(M[i],dis[u][d]);
for(int v:G[u]) if(dis[v][!d]>dis[u][d]+1) dis[v][!d]=dis[u][d]+1,que.push(mp(v,!d));
}
rep(j,1,C[i]) {
rep(k,0,1) {
D[R[i-1]+j][k]=dis[j][k];
if(dis[j][k]!=P) cmax(M[i],dis[j][k]);
}
}
}
sort(I+1,I+n+1,[&](int x,int y){ return C[x]<C[y]; });
int ans=((Solve(1)+Solve(2)-Solve(3))%P+P)%P;
printf("%d\n",ans);
}

BoundedOptimization TopCoder - 12294

考虑在最优情况下,某一些数在 \(\text{lowerbound}\) ,某一些在 \(\text{upperbound}\)

确定了这些数之后,对于那些处于 \((\text{lowerbound,upperbound})\) 之间的数,它们的值其实是在忽略了上下界的情况下能取到的最优情况

否则只要上下移动一点就可能达到一个更优的情况

那么考虑枚举每个数的状态在 \(\text{lowerbound,upperbound,(lowerbound,uppperbound)}\)

推论:在中间的数之间必然存在互相关系

假设存在两个数 \(x_i,x_j\) 之间没有互相关系,令其他数不变,

则答案式子可以表示为 \(ax_i+bx_j+c\) 的形式,改变两个数的值总能得到更优的情况

\[ \ \]

设处在中间位置的数为 \(x_1,\cdots,x_m\) ,其他数为 \(y_1,\cdots ,y_k\) ,每个数连到外面的权值总和为 \(s_i\)

发现在最优情况下, \(\sum x_i+\sum y_i =MaxSum\) ,那么就确定了 \(\sum x_i\) 的值,设为 \(Sum\)

那么答案就可以表示为 \(\begin{aligned}\frac{\sum_ix_i\cdot(Sum-x_i+2\cdot s_i)}{2}\end{aligned}+c\)

其中常数 \(c\) 是外面的数之间的总和

不考虑限制的情况下,最优情况是 \(x_i=\frac{Sum+s_i}{2}\)

此时,若 \(\sum x_i\ne Sum\) ,是不合法的,需要调整

而让每个数改变 \(d\) ,减少的答案都是 \(d^2\) (因为原来是在二次函数的最高点)

所以每个数都改变 \(\begin{aligned}\frac{\sum \frac{Sum+x_i}{2}-Sum}{m}\end{aligned}\) 是最优的

注意这里计算时都是忽略了 \(x_1,\cdots,x_m\)\(\text{lowerbound,upperbound}\) ,求出的值不一定合法

如果不合法说明至少有某个值该到上下界之后答案会更优,所以这次的答案不用考虑

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
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
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)

#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

const int N=30;
const db eps=1e-7;

int G[N][N];
int A[N],w[N];
db val[N];

class BoundedOptimization {
public:
double maxValue(vector <string> Expr, vector <int> L, vector <int> R, int Max) {
string E="";
for(string t:Expr) E+=t;
memset(G,0,sizeof G);
rep(i,0,E.size()-1) if(isalpha(E[i])) {
G[E[i]-'a'][E[i+1]-'a']=G[E[i+1]-'a'][E[i]-'a']=1;
i++;
}
int n=L.size();
db ans=0;
rep(S,0,pow(3,n)-1) {
int T=S,m=0;
db res=0,sum=0;
rep(i,0,n-1) {
w[i]=T%3;
if(!w[i]) A[++m]=i;
else val[i]=(w[i]==1?L[i]:R[i]),sum+=val[i];
T/=3;
}
int fl=sum<=Max;
rep(i,1,m) rep(j,i+1,m) if(!G[A[i]][A[j]]) fl=0;
if(!fl) continue;
db left=Max-sum;
rep(i,1,m) {
db c=left;
rep(j,0,n-1) if(w[j] && G[A[i]][j]) c+=val[j]*2;
val[A[i]]=c/2;
sum+=val[A[i]];
}
if(m){
db t=(sum-Max)/m;
rep(i,1,m) val[A[i]]-=t;
}
rep(i,0,n-1) if(val[i]<L[i]-eps || val[i]>R[i]+eps) fl=0;
if(!fl) continue;
rep(i,0,n-1) rep(j,i+1,n-1) if(G[i][j]) res+=val[i]*val[j];
cmax(ans,res);
}
return ans;
}
};

「USACO 2021 US Open Platinum」Routing Schemes

\(K=0\)

此时,我们只需要求合法的匹配路径数量,并且一个路径是从小到大的

由于题目保证一定存在合法路径,从 \(1\)\(n\) 考虑每一条 \((u,v),(v>u)\)

我们可以看成是很多个 \(S\) 在路径上被从 \(1-n\) 不断地推过去

设一个点的入度为

\(ind_v=\sum_{(u,v)} 1+[v为S]\)

\(outd_u=\sum_{(u,v)} 1+[u为R]\)

每次到达一个点,必然有其 \(ind_u=outd_u\) ,即推进来的 \(S\) 个数恰好等于出边个数

此时合法的分配这 \(outd_u\)\(S\) 的方案数就是 \(outd_u!\)

直接求 \(\prod outd_i!\) 即可


\(K=1\)

存在反边的图看起来十分难处理,不妨直接把反边断掉

假设断掉前包含环的路径为 \(S_1\rightarrow a\rightarrow b\rightarrow R_1 (a>b)\)

则断掉后的路径变成 \(S_1\rightarrow a\)\(b\rightarrow R\)

不妨将在 \(a\) 上额外添加一个 \(R\) ,在 \(b\) 上额外添加一个 \(S\)

此时,新的问题又只包含 \((u,v)(u<v)\) ,同 \(K=0\) 求解

理想情况下,新问题中的所有方案均可以通过将 \(a,b\) 相接还原

但是显然如果最终方案上 \(b\rightarrow a\) 相接就会成环

所以需要额外 \(dp\) 出包含 \(b\rightarrow a\) 的非法方案

考虑用类似 \(K=0\) 的办法,我们扫描每个 \(i\)\(S\) 向后推

\(dp_i\) 表示当前 \(dp\) 的路径最后一个点是 \(i\) 的方案数

我们希望结束点是 \(a\) ,开始点是 \(b\)

此时依次推过去 \(i\) ,此时只有 \(dp_{j},j\ge i\) 的情况是合法的

考虑 \(j=i\) 时,需要为 \(j\) 找一个归宿 \(k\) ,或者判定 \(j=a\) 时结束路径

此时,相当于在原图上使 \(outd_i\) 减少了 \(1\)

得到转移 \(dp_k\leftarrow dp_j\cdot (outd_i-1)!\)

\(j>i\) 时,不需要考虑 \(j\) 的变化

得到转移 \(dp_j\leftarrow dp_j\cdot outd_i!\)


\(K=2\)

有了 \(K=1\) 的铺垫,想必这里十分简单

设反边为 \((a,b),(c,d)\) ,显然加入两组 \(S,R\)

考虑新图上什么样的情况是不合法的

  1. \(b\rightarrow a\)

  2. \(d\rightarrow c\)

注意1,2是有交的

  1. \(b\rightarrow c\rightarrow d\rightarrow a\)

环交错扭在一起,这种情况比较容易漏掉

稍微容斥一下即可

复杂度分析:

扫描每个 \(i\) 时, \(dp_{x,y}\) 中满足 \(x=i\)\(y=i\) 的有 \(O(n)\) 个,转移每个需要 \(O(n)\) 时间

扫描每个 \(i\) 时, \(dp_{x,y}\) 中满足 \(x=i\)\(y=i\) 的有 \(O(1)\) 个,转移每个需要 \(O(n^2)\) 时间

因此复杂度为 \(O(n^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
98
99
100
101
102
103
104
105
106
107
const int N=110,P=1e9+7;

int n,k,J[N];
char s[N];
int G[N][N];

namespace pt1{
void Solve(){
int ans=1;
rep(i,1,n) ans=1ll*ans*J[deg[i]]%P;
printf("%d\n",ans);
}
}

int deg[N];
int Calc1(int a,int b){
static int dp[N];
// calculate stategies that contain b->a
memset(dp,0,sizeof dp);
dp[b]=1;
rep(i,1,n) {
drep(j,n+1,i) if(dp[j]) {
if(j==i) {
if(i==a) dp[n+1]=(dp[n+1]+1ll*J[deg[i]-1]*dp[j])%P;
else {
rep(k,i+1,n) if(G[i][k]) dp[k]=(dp[k]+1ll*J[deg[i]-1]*dp[j])%P;
}
} else dp[j]=1ll*dp[j]*J[deg[i]]%P;
}
}
return dp[n+1];
}

int Calc2(int a,int b,int c,int d){
// calculate strategies that contain both b->a,d->c
static int dp[N][N];
memset(dp,0,sizeof dp),dp[b][d]=1;
rep(i,1,n) {
drep(x,n+1,i) drep(y,n+1,i) if(dp[x][y]) {
int t=1ll*dp[x][y]*J[deg[i]-(x==i)-(y==i)]%P;
if(x!=i && y!=i) { dp[x][y]=t; continue; }
if(x!=i) {
if(y==c) dp[x][n+1]+=t,Mod1(dp[x][n+1]);
else rep(j,i+1,n) if(G[i][j]) dp[x][j]+=t,Mod1(dp[x][j]);
continue;
}
if(y!=i) {
if(x==a) dp[n+1][y]+=t,Mod1(dp[n+1][y]);
else rep(j,i+1,n) if(G[i][j]) dp[j][y]+=t,Mod1(dp[j][y]);
continue;
}
if(x==a) {
if(y==c) dp[n+1][n+1]+=t,Mod1(dp[n+1][n+1]);
else rep(j,i+1,n) if(G[i][j]) dp[n+1][j]+=t,Mod1(dp[n+1][j]);
continue;
}
if(y==c) {
rep(j,i+1,n) if(G[i][j]) dp[j][n+1]+=t,Mod1(dp[j][n+1]);
} else {
rep(j,i+1,n) if(G[i][j]) rep(k,i+1,n) if(G[i][k] && j!=k) dp[j][k]+=t,Mod1(dp[j][k]);
}
}
}
return dp[n+1][n+1];
}
namespace pt2{
void Solve(){
int a=-1,b=-1;
rep(i,1,n) rep(j,1,i-1) if(G[i][j]) a=i,b=j;
int ans=1;
rep(i,1,n) ans=1ll*ans*J[deg[i]]%P;
ans-=Calc1(a,b),Mod2(ans);
printf("%d\n",ans);
}
}

namespace pt3{
void Solve(){
int a=-1,b=-1,c=-1,d=-1;
rep(i,1,n) rep(j,1,i-1) if(G[i][j]) {
if(a==-1) a=i,b=j;
else c=i,d=j;
}
int ans=1;
rep(i,1,n) ans=1ll*ans*J[deg[i]]%P;
ans-=Calc1(a,b),Mod2(ans);
ans-=Calc1(c,d),Mod2(ans);
ans+=Calc2(a,b,c,d),Mod1(ans);
ans-=Calc2(c,b,a,d),Mod2(ans);
printf("%d\n",ans);
}
}

int main(){
rep(i,*J=1,N-1) J[i]=1ll*J[i-1]*i%P;
rep(_,1,rd()) {
n=rd(),k=rd(),scanf("%s",s+1);
rep(i,1,n) rep(j,1,n) scanf("%1d",G[i]+j);
rep(i,1,n) {
deg[i]=s[i]=='R';
rep(j,1,n) deg[i]+=G[i][j];
}
if(k==0) pt1::Solve();
else if(k==1) pt2::Solve();
else pt3::Solve();
}
}

CosmicBlocks - TopCoder- 12034 (网络流)

注意题目定义的同构是存在不同的颜色覆盖关系,而不是存在不同的排列顺序

所以先枚举每一层放了那些颜色,再枚举那些颜色之间有覆盖

每一层的颜色划分数很少,最多可能同时存在的覆盖关系是 \(9\) 种,枚举复杂度最多是 \(2^9\) ,然后可以 \(2^n\cdot n\ \text{dp}\) 出拓扑序列的个数

问题在于如何快速又方便地判断对于当前情况是否存在方案

一种方法是上下界网络流

按照层之间的关系,覆盖关系就连 \([1,+\infty)\) 的边,同时源点向 \(1\) 层的点连 \([0,+\infty)\) 的边,每个点都向汇点连 \([0,\infty)\) 的边

注意由于要限制流过每个点的流量,每个点要拆成两个点中间连 \([num_i,num_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
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include<bits/stdc++.h>
using namespace std;

#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define reg register
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)

#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int 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;
}



static const int N=20,INF=1e8+10;
int n,m,ans,L,R;
int cnt[N],id[N];
vector <int> G[N];
int GS[N];
vector <int> Layer[N];

int Calc_DAG() { // 计算拓扑序列的个数
static int dp[1<<6];
int A=(1<<n)-1;
rep(i,0,A) dp[i]=0;
dp[0]=1;
rep(S,0,A-1) rep(i,0,n-1) if((~S&(1<<i)) && (S&GS[i+1])==GS[i+1]) dp[S|(1<<i)]+=dp[S];
return dp[A];
}

int ind[N];
struct Limited_Flow{ // 有源汇可行流
static const int M=300;
int S,T;
struct Edge{
int to,nxt,w;
} e[M];
int head[N],ecnt;
void clear(){
rep(i,1,n*2+4) head[i]=0;
ecnt=0;
}
#define erep(u,i) for(int i=head[u];i;i=e[i].nxt)
void AddEdge(int u,int v,int w) { e[ecnt]=(Edge){v,head[u],w},head[u]=ecnt++; }
void Link(int u,int v,int w){ AddEdge(u,v,w),AddEdge(v,u,0); }
int dis[N];

int Bfs(){
static queue <int> que;
rep(i,1,T) dis[i]=INF; dis[S]=0,que.push(S);
while(!que.empty()) {
int u=que.front(); que.pop();
erep(u,i) {
int v=e[i].to,w=e[i].w;
if(!w || dis[v]<=dis[u]+1) continue;
dis[v]=dis[u]+1,que.push(v);
}
}
return dis[T]<INF;
}
int Dfs(int u,int flowin){
if(u==T) return flowin;
int flowout=0;
erep(u,i) {
int v=e[i].to,w=e[i].w;
if(!w || dis[v]!=dis[u]+1) continue;
int t=Dfs(v,min(flowin-flowout,w));
e[i].w-=t,e[i^1].w+=t,flowout+=t;
if(flowin==flowout) break;
}
if(!flowout) dis[u]=0;
return flowout;
}
int Dinic(){
int ans=0;
while(Bfs()) ans+=Dfs(S,INF);
return ans;
}
int Check(){
erep(S,i) if(e[i].w) return 0;
erep(T,i) if(e[i^1].w) return 0;
return 1;
}
} Flow;

void Extend_Edge(int u,int v,int L,int R){
ind[u]-=L,ind[v]+=L;
Flow.Link(u,v,R-L);
}

int Check(){
rep(i,1,n*2+4) ind[i]=0;
Flow.clear();
rep(i,1,n) Extend_Edge(i*2-1,i*2,cnt[i],cnt[i]);
rep(i,1,n) if(id[i]==1) Extend_Edge(n*2+1,i*2-1,cnt[i],cnt[i]);
rep(u,1,n) for(int v:G[u]) Extend_Edge(u*2,v*2-1,1,INF);
rep(i,1,n) Extend_Edge(i*2,n*2+2,0,INF);
Extend_Edge(n*2+2,n*2+1,0,INF);

rep(i,1,n*2+2) {
if(ind[i]>0) Flow.Link(n*2+3,i,ind[i]);
if(ind[i]<0) Flow.Link(i,n*2+4,-ind[i]);
}
Flow.S=n*2+3,Flow.T=n*2+4;

Flow.Dinic();
return Flow.Check();
}

void Dfs_GetDAG(int p) { // dfs枚举覆盖关系
if(p==n+1) {
int Ways=Calc_DAG();
if(Ways<L || Ways>R) return;
ans+=Check();
return;
}
if(id[p]==m) return Dfs_GetDAG(p+1);
int n=Layer[id[p]+1].size();
rep(S,0,(1<<n)-1) {
rep(i,0,n-1) if(S&(1<<i)) G[p].pb(Layer[id[p]+1][i]),GS[p]|=1<<(Layer[id[p]+1][i]-1);
Dfs_GetDAG(p+1);
G[p].clear(),GS[p]=0;
}
}

void Dfs_Getlayer(int num,int fir,int chosennumber){ // dfs枚举层的情况
if(chosennumber==n) {
m=num;
rep(i,1,m) Layer[i].clear();
rep(i,1,n) Layer[id[i]].pb(i);
Dfs_GetDAG(1);
return;
}
rep(i,fir,n) if(!id[i]) {
id[i]=num;
Dfs_Getlayer(num,i+1,chosennumber+1);
id[i]=0;
}
if(fir!=1) Dfs_Getlayer(num+1,1,chosennumber);
}

class CosmicBlocks {
public:
int getNumOrders(vector <int> a, int Min, int Max) {
L=Min,R=Max;
n=a.size(),ans=0; rep(i,0,n-1) cnt[i+1]=a[i];
Dfs_Getlayer(1,1,0);
return ans;
}
};

//Powered by KawigiEdit 2.1.4 (beta) modified by pivanof!

TopCoder SRM 570 Div1 CurvyonRails

题意: 一个 \(n\times m\) 的网格图,其中有一些点需要建铁路,有一些点为关键点,在关键点上修直铁路会产生1的代价,求最小的代价

由于 \(n,m\leq 25\) 显然不可以插头 \(\text{dp}\) 。。。

考虑轨道联通实际上类似网络流的形式

考虑一个常见的思路: 网格图可以简化为二分图 然后 跑网络流

先不考虑代价的问题,判断是否存在合法方案

每个格子要有两条出边,因此可以让 \(S\) 向左侧点连边权为2的边,右侧点向 \(T\) 连边权为2的边

然后可以让每个左侧点向相邻的右侧点连边,即考虑了联通关系

下面考虑代价的计算,加入边的代价,即为费用流

连同向边会产生代价,因此考虑为每个节点新增两个节点,表示向上下/左右连边

对于让原节点对于新增的上下和左右节点 分别连两条代价为0和1的边

这样如果流了同向边,就会产生代价

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 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,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int 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=25*30*3,M=N<<3,INF=1e9+10;

int n,m,k;
int a[30][30];

// Max Flow Min Cost

int id[30][30][2];
struct Edge{
int to,nxt,w,c;
}e[M];
int head[N],ecnt,S,T,V;
void Clear(){
rep(i,1,V) head[i]=0;
ecnt=1,V=0;
}
void AddEdge(int u,int v,int w,int c) {
e[++ecnt]=(Edge){v,head[u],w,c};
head[u]=ecnt;
}
void Link(int u,int v,int w,int c=0){ AddEdge(u,v,w,c),AddEdge(v,u,0,-c); }
#define erep(u,i) for(int i=head[u],v=e[i].to,w=e[i].w,c=e[i].c;i;i=e[i].nxt,v=e[i].to,w=e[i].w,c=e[i].c)

int dis[N];
int SPFA(){
static int inq[N];
static queue <int> que;
rep(i,1,V) dis[i]=INF;
que.push(S),dis[S]=0;
while(!que.empty()) {
int u=que.front(); que.pop();
inq[u]=0;
erep(u,i) if(w && dis[v]>dis[u]+c) {
dis[v]=dis[u]+c;
if(!inq[v]) inq[v]=1,que.push(v);
}
}
return dis[T]<INF;
}
int Dfs(int u,int in) {
if(u==T) return in;
int out=0,t=dis[u]; dis[u]=INF;
erep(u,i) if(w && dis[v]==t+c) {
int t=Dfs(v,min(in-out,w));
e[i].w-=t,e[i^1].w+=t,out+=t;
if(in==out) break;
}
if(out) dis[u]=t;
return out;
}

Pii Dinic(){
int flow=0,cost=0;
while(SPFA())
for(int t;(t=Dfs(S,INF));) flow+=t,cost+=dis[T]*t;
return mp(flow,cost);
}

class CurvyonRails {
public:
int getmin(vector <string> field) {
n=field.size(),m=field[0].size();
rep(i,0,n-1) rep(j,0,m-1) a[i+1][j+1]=field[i][j];
k=0,Clear();
rep(i,1,n) rep(j,1,m) if(a[i][j]!='w') {
k++;
rep(d,0,1) id[i][j][d]=++V;
}
S=++V,T=++V;
rep(i,1,n) rep(j,1,m) if(a[i][j]!='w') {
if((i+j)&1){
Link(S,++V,2,0);
rep(d,0,1) {
Link(V,id[i][j][d],1,0);
Link(V,id[i][j][d],1,a[i][j]=='C');
// 如果两条走了同向,就会产生1的代价
}
if(i<n && a[i+1][j]!='w') Link(id[i][j][0],id[i+1][j][0],1,0);
if(j<m && a[i][j+1]!='w') Link(id[i][j][1],id[i][j+1][1],1,0);
} else {
Link(++V,T,2,0);
rep(d,0,1) {
Link(id[i][j][d],V,1,0);
Link(id[i][j][d],V,1,a[i][j]=='C');
// 如果两条走了同向,就会产生1的代价
}
if(i<n && a[i+1][j]!='w') Link(id[i+1][j][0],id[i][j][0],1,0);
if(j<m && a[i][j+1]!='w') Link(id[i][j+1][1],id[i][j][1],1,0);
}
}
Pii ans=Dinic();
if(ans.first!=k) return -1;
return ans.second;
}
};
//Powered by KawigiEdit 2.1.4 (beta) modified by pivanof!

Equilateral Triangles

题意: 求所有三个点哈密顿距离相等的无序三元点对个数

推论1: 到平面中一点 \((x,y)\) 哈密顿距离为 \(d\) 的点,构成一个以 \((x,y)\) 为中心, \(d\) 为半对角线长的菱形

菱形不好搞,转一下,令旋转后的点 \(x'=x+y,y'=x-y\) ,(也就是曼哈顿距离转切比雪夫距离)

这样得到的就是一个正方形了

推论2: 选出的三点中,一定存在两个点 \(x\) 或者 \(y\) 坐标相同

如果选出点 \(A,B\) 不满足,由下图可以看到,可行的部分(就是相交部分 \(C1,C2\) )也必然满足

te1.png

接下来以 \(x\) 相同为例,枚举点 \(A,B\) ,设其距离为 \(d\)

te2.png

可以看到比较显然就是两条红色的相交线段, \(O(n^3)\) 枚举 \(A,B\) 两点后,直接用前缀和维护即可

对于 \(x\) 相同,对于 \(y\) 相同分别做一次即可,注意考虑的时候不要把 \(x,y\) 都有相同的算两次

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
#include<bits/stdc++.h>
using namespace std;
#pragma GCC optimize(2)
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)

const int N=610;

int n;
char A[N][N],B[N][N];
int S[N][N];
int D[N],C;

int main() {
scanf("%d",&n);
rep(i,1,n) {
scanf("%s",A[i]+1);
rep(j,1,n) if(A[i][j]=='*') B[i+j][i-j+n]=1;
}
int ans=0;
rep(i,1,n*2) rep(j,1,n*2) S[i][j]=S[i][j-1]+B[i][j];
rep(i,1,n*2) {
C=0;
rep(x,1,n*2) if(B[i][x]) D[++C]=x;
rep(a,1,C) rep(b,a+1,C) {
int d=D[b]-D[a];
i-d>=1 && (ans+=S[i-d][D[b]]-S[i-d][D[a]-1]);
i+d<=n*2 && (ans+=S[i+d][D[b]]-S[i+d][D[a]-1]);
}
}
rep(i,1,n*2) rep(j,1,n*2) S[i][j]=S[i][j-1]+B[j][i];
rep(i,1,n*2) {
C=0;
rep(x,1,n*2) if(B[x][i]) D[++C]=x;
rep(a,1,C) rep(b,a+1,C) {
int d=D[b]-D[a];
i-d>=1 && (ans+=S[i-d][D[b]-1]-S[i-d][D[a]]);
i+d<=n*2 && (ans+=S[i+d][D[b]-1]-S[i+d][D[a]]);
}
}
printf("%d\n",ans);
}





Topcoder SRM568 Div1 DisjointSemicircles (二分图染色)

题意: 给定数轴上排列的 \(2n\) 个点,每个点需要找到另一个点和它匹配,并且以他们为直径两端,向上或者向下作一个半圆

有一些点已经匹配好了,要求判断是否存在一个合法的方案,满足所有的半圆不相交

###思路:

枚举已经确定的匹配半圆的方向(设有 \(m\) 对已匹配),然后 \(O(n)\) 判断自由点是否存在合法方案

判断合法方案的核心性质:

定义一个点的方向为其所连接的半圆的方向(上为0,下为1)

则自由点存在合法方案的充要条件是:

整个序列上每种方向的点数为偶数,且所有已匹配的半圆所覆盖的区间下,和半圆同向的点个数为偶数

必要性:

如果某个半圆下同向点个数为奇数,则必然有一个点与其同向并且不得不连到区间外,这显然不合法

充分性:

一种合法的构造方法是:

按照 \(L\) 从左到右,遍历每个已匹配的半圆,如果包含同向子半圆优先解决同向的子半圆

剩下的点依然是偶数个,从左到右依次和上一个匹配即可

\[\ \]

判断是否存在合法方案

那么问题转化为判断是否存在一种合法的定向方案,使得某一些区间里0/1的个数为偶数

考虑构建二分图染色,令点集 \(V=\{0,1,\cdots,n,0',1',\cdots,n'\}\) ,则 \((u,v)\in E\) 表示 \(col(u)\ne col(v)\)

其中 \(i\) 号节点表示 \(1-i\) 中所有未匹配节点方向的异或和, \(i'\) 表示 \(i\) 的反点 \((i,i')\in E\)

(到这里可以自己想一下怎么连边)

对于已匹配圆 \([L,R]\) (注意不要忘了 \([1,n]\) )

如果它方向为 \(1\) ,显然只需要 \(col(L-1)=col(R)\)

如果方向为0,设 \([L,R]\) 未染色个数为 \(k\) ,则显然有 \(col(L-1)=col(R)\oplus (k\mod 2)\) ,即考虑反向的个数

同时对于已匹配点 \(i\) ,显然有 \(col(i)=col(i-1)\)

由此,得到一个 \(O(n)\) 点数边数的图

如果在 \(\text{dfs}\) 枚举时同步加边和回撤,总复杂度就为 \(O(2^m\cdot 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
#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)

const int N=110;

int n;
int a[N];
int cnt[N];
int L[N],R[N],m,Cross[N][N];
int vec[2][N],c[2];

struct Edge{
int u,v,nxt;
}e[N*10];
int head[N],ecnt;
void AddEdge(int u,int v) {
e[++ecnt]=(Edge){u,v,head[u]};
head[u]=ecnt;
}
void Link(int u,int v){
AddEdge(u,v),AddEdge(v,u);
}
void Back(){
head[e[ecnt].u]=e[ecnt].nxt,ecnt--;
head[e[ecnt].u]=e[ecnt].nxt,ecnt--;
}

int ans,fl;
int vis[N];
void dfs_col(int u,int c){
vis[u]=c;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!vis[v]) dfs_col(v,3-c);
else if(vis[v]==vis[u]) fl=0;
}
}

void dfs(int p) {
if(ans) return;
if(p>m){
rep(i,0,n*2+1) vis[i]=0;
fl=1;
rep(i,0,n*2+1) if(!vis[i]) dfs_col(i,1);
ans|=fl;
return;
}
rep(i,0,1){
int fl=1;
rep(j,1,c[i]) if(R[vec[i][j]]>L[p] && R[vec[i][j]]<R[p]) fl=0;
if(!fl) continue;
vec[i][++c[i]]=p;
if(i || ~(cnt[R[p]]-cnt[L[p]])&1) Link(L[p]+n+1,R[p]-1);
else Link(L[p],R[p]-1);
dfs(p+1);
c[i]--,Back();
}
}

class DisjointSemicircles {
public:
string getPossibility(vector <int> labels) {
n=labels.size(),m=0;
rep(i,1,n) a[i]=labels[i-1];
rep(i,1,n) {
cnt[i]=cnt[i-1]+(a[i]==-1);
if(~a[i]) rep(j,i+1,n) if(a[j]==a[i]) L[++m]=i,R[m]=j;
}
if(!m) return "POSSIBLE";
rep(i,0,(n+1)*2) head[i]=ecnt=0;
rep(i,1,n) if(~a[i]) Link(i+n+1,i-1);
rep(i,0,n) Link(i,i+n+1);
Link(n+1,n);
ans=0,dfs(1);
return ans?"POSSIBLE":"IMPOSSIBLE";
}
};


MapGuessing TopCoder - 12152

做得我很迷

首先是可以把问题转化为,每次操作之后会让原序列的限制条件变为:不考虑某一些位置时合法

枚举每个开始位置,依次考虑每一个操作,如果有一个位置被改为不同,就是不合法的

对于每一个开始位置,能得到的的最优限制条件都是唯一的,因为只要是合法的,一定取最后一个合法的位置,才能尽可能多地覆盖一些位置

那么我们得到了 \(|goal|\) 个这样的限制条件 \(S_i\) ,设 \(n=|goal|\)

直接计算肯定会算重,考虑一个简单的容斥

\(\begin{aligned}Answer=\sum_{T\ne \empty}(-1)^{|T|+1}2^{|S_{T_1}\cap \cdots \cap S_{T_{|T|}}|}\end{aligned}\)

就是枚举选择一个限制的集合,求出他们的并集

直接枚举复杂度当然是 \(O(2^{|n|})\) ,如果 \(\text{dfs}\) 枚举,当前状态为 \(0\) 时,可以直接返回答案

估计一下这个 \(\text{dfs}\) 的复杂度

设操作过程中指针左右移动的距离是 \(L\) ,那么最多存在 \(n-L\) 个合法的开始位置,每个状态最多包含 \(L\)\(1\)

很显然枚举的上限是 \(\min\{2^{n-L},2^{L}\}\) ,即受到开始位置的个数可能出现的1个数的限制

\(n-L=L\) 时,复杂度达到上限是 \(O(2^{\frac{n}{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
#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)

set <ll> st;
ll S[40];
int now[40],cnt;
ll dfs(int p,ll State){
if(p==cnt+1) return (1ll<<__builtin_popcountll(State)); // 枚举完毕
if(State==0) return 0; // 剪枝,注意因为是后面的所有1,-1相加,所以是return 0而不是1
return dfs(p+1,State)-dfs(p+1,State&S[p]); // 直接处理容斥系数
}

class MapGuessing {
public:
long long countPatterns(string S, vector <string> code) {
string C="";
for(string t:code) C+=t;
int n=S.size();
st.clear();
rep(i,0,n-1) {
int p=i,f=1;
ll lst=0; // 记录最后一个合法的即可
rep(i,0,n-1) now[i]=0;
for(char c:C) {
if(c=='<') p--;
if(c=='>') p++;
if(c=='0') now[p]=1;
if(c=='1') now[p]=2;
if(p<0 || p>=n){ f=0; break; }
int fl=1;
ll T=0;
rep(i,0,n-1) {
if(!now[i]) continue;
if(now[i]-1!=S[i]-'0'){ fl=0; break; }
else T|=1ll<<i;
}
if(!fl) continue;
lst=T;
}
if(!f) continue;
st.insert(-lst);
}
cnt=0;
for(ll x:st) {
ll y=-x;
int f=1;
rep(i,1,cnt) if((::S[i]&y)==y){ f=0; break; }
if(f) ::S[++cnt]=y;
}
//初始系数是-1
ll ans=(1ll<<n)-dfs(1,(1ll<<n)-1); // 注意枚举出来会把T=emptyset的情况算进去,要去掉
return ans;
}
};

TopCoder SRM 561 Orienteering 树形dp

题意: 给定了一棵树,以及树上一些节点为关键点,求出随机选出 \(k\) 个关键点后遍历它们的最短路径的期望

遍历关键点相当于要遍历一棵树,考虑遍历一棵树的最优决策

假设我们确定了一个根 \(u\) ,递归考虑每棵子树的问题

发现除了最后留在的那个点对应的路径 \((u,v)\) 以外,所有的边都要被遍历两次

即答案 \(\sum _{e\in E} 2\cdot w(e)-dis(u,v)\)

所以改变根就会发现,答案就是总长*2-直径长度

设总点数为 \(n\) ,包含的总关键点数为 \(m\) ,要选出 \(k\) 个点

Part1 总长计算

考虑对于每一条边计算产生的树跨过它的概率

设这条边两边的关键点的个数分别为 \(a,b(a+b=m)\)

显然,这条边被跨过的概率就是

\(\begin{aligned} 1-\frac{C(a,k)}{C(m,k)}-\frac{C(b,k)}{C(m,k)}\end{aligned}\) (即减去所有选出的关键点都在两边的概率)

\(\begin{aligned} f(i)=\frac{C(i,k)}{C(m,k)}=\frac{i!(m-k)!}{m!(i-k)!}\end{aligned}\)

因为这个题目要计算double,所以求阶乘的精度会比较有问题

考虑递推求出 \(f(i)\) ,则有

\(f(i)=\left\{\begin{aligned}1 && i=m \\ \frac{f(i+1)(i+1-k)}{i+1} && i<m\end{aligned}\right.\)

这样递推就能比好得保证精度,然后直接对于每条边计算即可,复杂度为 \(O(n)\)

\[ \ \]

Part2 直径长度计算

直径似乎是一个很难在树形 \(\mathrm{dp}\) 中确定的东西,因此考虑直接先枚举直径的两个端点

定义一棵树的直径两端点为最小的二元组 \((A,B)\) 满足

\(\begin{aligned} A<B,dis(A,B)=\max_{u,v\in V} \{dis(u,v)\}\end{aligned}\)

因为 \(k>1\) ,所以两端点一定不同,不妨设两个端点分别为 \(A,B(A<B)\)

则一个点 \(C\) 可以出现在树上的充要条件是

\((dis(A,C)>dis(A,B) \or dis(A,C)=dis(A,B)\and C \ge B)\)

\(\and (dis(B,C)>dis(A,B) \or dis(B,C)=dis(A,B)\and C \ge A)\)

数出所有可以出现在树上的点个数 \(i\) ,则 \((A,B)\) 为直径的概率应为 \(\frac{C(i-2,k-2)}{C(m,k)}\)

即强制选取了 \((A,B)\) 两个点

依然考虑递推求出

\(\begin{aligned} f(i)=\frac{C(i-2,k-2)}{C(m,k)}=\frac{(i-2)!k(k-1)(m-k)!}{m!(i-k)!}\end{aligned}\)

类似地,得到其递推式为

\(f(i)=\left\{\begin{aligned}\frac{k(k-1)}{m(m-1)} && i=m \\ \frac{f(i+1)(i+1-k)}{i-1} && i<m\end{aligned}\right.\)

这一部分复杂度为 \(O(m^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
#include<bits/stdc++.h>
using namespace std;

typedef pair <int,int> Pii;
typedef long long ll;
typedef double db;
#define mp make_pair
#define pb push_back
#define reg register
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#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,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int 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=51;

int n,m,k;
int id[N][N],mk[2510],sz[2510],p[310];
vector <int> G[2520];
db dp[2510],ans;
int dis[2500][2500];

void Dfs(int u,int f) {
sz[u]=mk[u]>0;
for(int v:G[u]) if(v!=f) {
Dfs(v,u),sz[u]+=sz[v];
ans+=1-dp[sz[v]]-dp[m-sz[v]];
}
}
void Dfs_Getdis(int rt,int u,int f){
for(int v:G[u]) if(v!=f)
dis[rt][v]=dis[rt][u]+1,Dfs_Getdis(rt,v,u);
}

int A,B;

int Check(int u){
if(dis[A][u]>dis[A][B]) return 0;
if(dis[A][u]==dis[A][B] && u<B) return 0;
if(dis[B][u]>dis[A][B]) return 0;
if(dis[B][u]==dis[A][B] && u<A) return 0;
return 1;
}

class Orienteering {
public:
double expectedLength(vector <string> field, int K) {
rep(i,1,n) G[i].clear(); memset(id,0,sizeof id),memset(mk,0,sizeof mk);
n=m=0,k=K;
rep(i,0,field.size()-1) rep(j,0,field[i].size()-1) if(field[i][j]!='#') {
id[i][j]=++n;
if(field[i][j]=='*') p[mk[n]=++m]=n;
}
rep(i,0,field.size()-1) rep(j,0,field[i].size()-1) if(id[i][j]) {
if(i<iend && id[i+1][j]) G[id[i][j]].pb(id[i+1][j]),G[id[i+1][j]].pb(id[i][j]);
if(j<jend && id[i][j+1]) G[id[i][j]].pb(id[i][j+1]),G[id[i][j+1]].pb(id[i][j]);
}
rep(i,0,m) dp[i]=0;
dp[m]=1;
drep(i,m-1,k) dp[i]=dp[i+1]/(i+1)*(i+1-k);
ans=0,Dfs(1,0),ans*=2; // 期望总长
// 下面求期望直径长度
rep(i,1,n) if(mk[i]) Dfs_Getdis(i,i,dis[i][i]=0);

dp[m]=1.0*k*(k-1)/m/(m-1);
drep(i,m-1,k) dp[i]=dp[i+1]/(i-1)*(i+1-k);
rep(i,1,m) rep(j,i+1,m) {
int c=0; A=p[i],B=p[j];
rep(k,1,m) c+=Check(p[k]);
if(c>=k) ans-=dis[A][B]*dp[c];
}
return ans;
}
};


\(\begin{aligned} \frac{\begin{aligned} \sum_{i=0}^{min(k,sz[u]) } C(sz[u],i)\cdot C(m-sz[u],k-i)\end{aligned} }{C(m,k)}\end{aligned}\)

\(\begin{aligned} \frac{\begin{aligned} \sum_{i=0}^{min(k,sz[u]) } sz[u]!(m-sz[u])!k!(m-k)!\cdot \end{aligned} }{m!i!(sz[u]-i)!(k-i)!(m-sz[u]-k+i)!}\end{aligned}\)

\(\begin{aligned} 1-\frac{C(sz[u],k)}{C(m,k)}-\frac{C(m-sz[u],k)}{C(m,k)}\end{aligned}\)

\(\frac{C(i,k)}{C(m,k)}=\frac{i!(m-k)!}{m!(i-k)!}\)

i从m for 到 k

f(i)=f(i+1)/i(k-i+1)

\(\frac{C(i-2,k-2)}{C(m,k)}=\frac{(i-2)!k(k-1)(m-k)!}{m!(i-k)!}\)

i=m时, \(f(m)=\frac{k(k-1)}{m(m-1)}\)

0%