(T1) 不会轮廓线 (dp),呜呜呜。
(T2) 想到了要把环单独处理一下,但做法是 (O(环的大小^2)) 的,担心被卡就没写。
(T3) 往容斥和二项式反演方面去想,想了半天没想出来。
T1 Gird
题目内容
你有一个 (n) 行 (m) 列的教室,其中某些位置不能坐人。
你的班上有 (k) 对 CP,你对八卦早已失去了兴趣,转而想要知道有多少种给这 (2k) 位同学排座位的方式,使得每对 CP 的座位不相邻(有些位置可能会空出来,方案不同当且仅当某个位置状态不同,即坐着不同的同学)。
结果对 (10^9+7) 取模。
数据范围:(nmleq 144)
solution
首先注意到 (nmleq 144) ,那么就意味着 (min(n,m)leq 12) ,然后我们就可以状压 (dp) 了。
设 (f(pos,S,x)) 表示做到 (pos) 位置,最近这 (m) 个位置的状态是 (S),已经有 (x) 对的方案数。
在反演一下就可以得到答案,巴拉巴拉。。。
T2 cactus
题目内容
你有一棵仙人掌,你想要知道所有点两两最短路之和,即,(sumlimits_{i<j} dis(i,j))。
仙人掌是一张无向简单连通图,无重边自环,且每条边属于至多一个简单环。
本题中边没有权值,即可以认为边权均为 (1)。
数据范围:(nleq 3 imes 10^5,mleq 6 imes 10^5)
solution
这道题正解思路挺好想的,但是怎么处理比较关键。(O(环的大小^2)) 的做法好像被出题人点名卡了。
首先我们可以对整棵树求一个 (DFS) 树,然后对于环上的边和树边单独考虑。
首先对于树边的情况,对答案的贡献显然为:(siz[x] imes (n-siz[x]))
然后考虑怎么处理环的情况,我们把整张图画出来,他长成这个样子:
也就是环上每个点下面都挂着若干个点组成的子树,设这个子树大小为 (f[i]) 。
不难发现两个点对答案的贡献为:(f[i] imes f[j] imes min(j-i,n-(j-i)))
考虑两点之间的路径被经过多少次,就可以得出来上面的那个柿子了。
然后我们的问题就变成可怎么在 (O(n)) 的时间内求出环上两个点之间的贡献。
有一个 (nlog n) 的 (FFT) 做法,我不太会写,就被咕掉了。
假设我们现在枚举到了 (i) 点,然后我们要算 (displaystyle sum_{j>i} f[i] imes f[j] imes min(j-i,n-(j-i)))
对于后面的那个取 (min) 就是两点之间的距离,考虑求一个 (k) 使得 (i+1- k) 的点到 (i) 点的距离为 (j-i),
而 (k+1,n) 的点到 (i) 点的距离为 (n-(j-i)) 。
然后我们把两个柿子拆一下变为:
(displaystyle sum_{j=i+1}^{k} f[i] imes f[j] imes (j-i) = displaystyle f[i] imes left( sum_{j=i+1}^{k} f[j] imes j- f[j] imes i ight) = f[i] imes left(sum_{j=i+1}^{k} f[j] imes [j] - i imes sum_{j=i+1}^{k} f[j] ight))
(displaystylesum_{j=k+1}^{n} f[i] imes f[j] imes(n-j+i) = f[i] imesleft(sum_{j=k+1}^{n} f[j] imes (n+i)-f[j] imes j ight) = f[i] imes left(left(n+i ight) imes sum_{j=k+1}^{n} f[j] - sum_{j=k+1}^{n} f[j] imes j ight))
然后你就会发现,只需要维护一下 (f[i]) 以及 (f[i] imes i) 的一个前缀和就好了。
复杂度:(O(n+m))
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define int long long
#define mp make_pair
const int N = 3e5+10;
int n,m,u,v,tot,ans,num,cnt,top;
int head[N],dfn[N],sum1[N],sum2[N],siz[N],sta[N],fa[N];
pair<int,int> p[N<<1];
bool vis[N];
struct node
{
int to,net;
}e[N<<2];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void add(int x,int y)
{
e[++tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
void dfs(int x,int ff)
{
dfn[x] = ++num; siz[x] = 1; fa[x] = ff;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(!dfn[to])
{
dfs(to,x);
siz[x] += siz[to];
}
else if(to != ff && dfn[to] < dfn[x]) p[++cnt] = mp(x,to);
}
}
signed main()
{
// freopen("cactus.in","r",stdin);
// freopen("cactus.out","w",stdout);
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
u = read(); v= read();
add(u,v); add(v,u);
}
dfs(1,0);
for(int i = 1; i <= cnt; i++)
{
int x = p[i].first, to = p[i].second, siz_son = 0; top = 0;
for(; x != to; x = fa[x])
{
sta[++top] = siz[x]-siz_son;
siz_son = siz[x]; vis[x] = 1;
}
sta[++top] = n-siz_son;
for(int i = 1; i <= top; i++) sum1[i] = sum1[i-1] + sta[i], sum2[i] = sum2[i-1] + sta[i] * i;
for(int i = 1; i <= top; i++)
{
int j = min(top,i+top/2);
ans += sta[i] * (sum2[j]-sum2[i] - (sum1[j]-sum1[i])*i);//i+1...j
ans += sta[i] * ((sum1[top]-sum1[j])*(top+i) - (sum2[top]-sum2[j]));//j+1...n
}
}
for(int i = 1; i <= n; i++) if(!vis[i]) ans += siz[i] * (n-siz[i]);
printf("%lld
",ans);
fclose(stdin); fclose(stdout);
return 0;
}
T3 color
题目内容
你有一个 (2) 行 (n) 列的网格,你要给每个格子染上三种颜色(红绿蓝)之一。
你还提了很多要求:任意两个相邻格子颜色不能相同;任何一个 (2 imes 2) 的块里,每种颜色都必须出现过。
现在你还想求出,恰好分别染了 (R,G,B) 个红、绿、蓝格子的方案数,结果对 (10^9+7) 取模。
数据范围:(nleq 5 imes 10^5,R,G,Bleq 2 imes n, R+G+B = 2n)
solution
组合计数。
首先我们构造一个序列 (a[i]) 表示第 (i) 列没有出现过的颜色。
设红绿蓝在序列 (a) 中出现次数为 (r,g,b), 这个根据 (R,G,B) 很容易就可以求出来。
不难发现题目中的条件等价于序列 (a[i]) 中相邻的两个不同且红绿蓝格子恰好出现 (r,g,b) 次。
然后我们就可以组合计数了。
首先考虑把红色分为 (i) 组,方案数显然为 ({r-1}choose i-1) ,如图:
接着我们考虑把绿色格子往每组之间的空隙中插,显然绿色格子的组数只能有 (i-1,i,i+1) 三种取值。
枚举 (jin{{i-1,i,i+1}}) ,那么方案数为 ({g-1choose j-1}) 。
注意当 (j=i) 的时候,我们此刻既可以往前面放又可以往后面放,所以方案数还有乘上 (2) 。
处理完上面两步后,情况就变为了:
不难发现相连的两个颜色相同的位置有 (r-i+g-j) 个,显然我们肯定是要往这些位置都放上一个蓝色小球的。
那么剩下的 (b-(r-i+g-j)) 个蓝色只能放在相邻的两个颜色不同的地方,显然这样的位置有 (i+j-1) 个。
所以方案数为 ({b-(r-i+g-j)}choose i+j-1) 。
这样我们就考虑到所有的情况了。
计算式:(displaystyle sum_{i=1}^{r}sum_{jin{i-1,i,i+1}} (1+[i=j]) imes {{r-1}choose i-1} imes {{g-1}choose j-1} imes {{b-(r-i+g-j)}choose i+j-1}) 。
直接 (O(n)) 算即可。
注意:在计算组合数的时候,可能会出现 (m<0) 或者 (n<m) 的情况,这时候特判一下就好了。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int p = 1e9+7;
int n,R,G,B,r,g,b,ans,jz[10000010],inv[10000010];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
int ksm(int a,int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
int C(int n,int m)
{
if(n < 0 || m < 0 || n < m) return 0;
return jz[n] * inv[m] % p * inv[n-m] % p;
}
signed main()
{
n = read(); R = read(); G = read(); B = read();
r = n-R, g = n-G, b = n-B;
jz[0] = inv[0] = 1;
for(int i = 1; i <= 2*n; i++) jz[i] = jz[i-1] * i % p;
inv[2*n] = ksm(jz[2*n],p-2);
for(int i = 2*n-1; i >= 1; i--) inv[i] = inv[i+1] * (i+1) % p;
for(int i = 1; i <= r; i++)
{
for(int j = i-1; j <= i+1; j++)
{
ans = (ans + (1LL+(i==j)) * C(r-1,i-1) % p * C(g-1,j-1) % p * C(i+j+1,b-(r+g-i-j)) % p) % p;
}
}
printf("%lld
",ans*2%p);
return 0;
}