打的很顺的一场
复盘
7:40 开题,看到题目名很interesting
T1 看起来很典,中位数显然考虑二分,然后就是最大子段和;T2 构造?一看数据范围这么小,感觉不是很难做;T3 神秘数据结构;T4 看到数据范围,这不是我们广义串并联图吗?不过感觉应该有别的做法
7:50 开写,T1 很快过了大样例,感觉不是很能挂
8:00 看 T2,显然有 2 n + m 2^{n+m} 2n+m 的暴搜,然后先枚举行,每一列的值就定了,然后就是个背包?显然折半搜索。没什么细节,8:40 交了
看 T3,删除操作显然倒过来变成加边; 2 2 2 操作显然是问是否在一个边双里,可以先把最终状态下边双缩点,然后开始手玩,发现加一条边可以看作把树上两点间路径缩成一个点,这个过程有点抽象啊
上个厕所,冷静一下发现这个其实是能做的,直接从两端点暴力往上跳,每次合并给当前点的父亲,改编号的操作可以启发式来维护。这样就有 O ( n log n + n α ( n ) ) O(n\log n+n\alpha (n)) O(nlogn+nα(n)) 的做法了, 8 × 1 0 5 8\times 10^5 8×105 应该不会卡 log \log log 吧
9:10 开写了,写完了困难的 tarjan 后猛然意识到图不连通!发现这就有点难做了,合并两棵树是不好做的,树的形态不固定没法找 LCA
再冷静一会,其实我可以把这些树边都保留下来?应该不影响答案,太对了啊
过程中发现启发式根本没必要,并查集直接维护即可
写写写,到最后一步 两个点往上跳, 先写了个暴力的做法验了验有没有写挂,发现确实挂了
改过后发现满数据竟然只跑 2.4 s 2.4s 2.4s?可是我这最坏复杂度 n 2 n^2 n2 ?
还是改成复杂度正确的做法了,交了,此时 10:10
本地大样例跑 2.1 s 2.1s 2.1s,不是吧我都没 log \log log 了还这么慢?考虑卡常
加上了手写快读,本地变成 1.3 s 1.3s 1.3s 了,可是时限 1 s 1s 1s
尝试输出运行时间,发现光读入就用了 0.6 s 0.6s 0.6s !于是立刻申请超级快读
拿到了超级快读的我发现读入只用 0.06 s 0.06s 0.06s,跑大样例只用 0.7 s 0.7s 0.7s
此时 10:30,开 T4 !
发现很唐的 B ( n ) B(n) B(n) 做法给了 50pts,然后是树上似乎也不是很难
很快写完了暴搜,发现 B ( n ) B(n) B(n) 不仅能跑过 n = 12 n=12 n=12, n = 13 , 14 n=13,14 n=13,14 也可以
于是启发我们广义串并联图缩完点后直接暴力,发现可以获得比树的做法多整整 10pts 的好成绩
中间又去想了想正解怎么做,类似毒瘤那个题,大概两个方向:要么就是广义串并联图,但缩完点后的暴力得优化一下;要么考虑生成树,然后加边
前者想了很久到最后最快只能得到 3 n 3^n 3n 的做法,后者往容斥去想,发现信息根本没法维护,就算按 dfs 序全是返祖边感觉也记录不了
决定写更简单的树,显然直接 d p dp dp,11:40 交了
剩下 20min 写广义串并联也写不完了,就又想了想正解怎么做,无果
结果是
100 + 100 + 100 + 70 = 370
差点挂分
看了赛时提交,T2 同一个做法,用 scanf
的得了 60,用 read
的得了 90,用 超级快读 的得了 100,逆
发现 T3 其实没必要写 tarjan,直接建最终的那棵最大生成树,然后正常的去缩环就行。( 没事复习了 tarjan 显然不亏啊!
T4 还是没想到正解啊,最后还是有点理不清哪些信息是重要的,需要维护的
题解
T3
T4
n
≤
12
n\leq 12
n≤12,注意要用集合划分的方式去搜,不会 TLE
从树上做法和暴力,我们可以拓展:
考虑往树上加边,不妨认为加的都是返祖边(后来发现这个没什么用),那么该边的权值与相连两个点的颜色有关
设 N = m − n + 1 N=m-n+1 N=m−n+1,发现这样的点只有 2 N 2N 2N 个,也就是说,我们关注颜色的点只有这么多,剩下的可以在 dp 里算贡献
那么考虑对于这 2 N 2N 2N 个点集合划分,接下来效仿树上做法,设状态 f i , c f_{i,c} fi,c 表示 i i i 为 c c c 颜色时子树地贡献,特别地,若 c = 0 c=0 c=0,代表 i i i 的颜色与划分出的集合都不相同
转移小分讨一下。对于这 2 N 2N 2N 个点的限制,实际上只需把 f f f 数组的初值特殊赋即可
这个做法的复杂度加上前缀和优化就是 B ( 2 N ) N n B(2N)Nn B(2N)Nn
进一步优化:是否真的需要 2 N 2N 2N 个点的颜色信息呢?其实不用,对于每条边只要有一个点的颜色枚举即可,另一个点可以在 d p dp dp 到这个点时根据颜色直接算贡献,乘到答案里
就 ok 了
int n , m , K ;
struct nn
{
int x , y , A , B ;
}e[N] ;
LL ksm( LL a , LL b )
{
LL res = 1 , t = a % mod ;
while( b ) {
if( b&1 ) res = res * t % mod ;
b = b >> 1 ;
t = t * t % mod ;
}
return res ;
}
namespace subtask1
{
const int N1 = 15 ;
int w[N1] ;
LL ans , g[N] ;
void calc( int cnt )
{
LL res = g[cnt] ;
for(int i = 1 ; i <= m ; i ++ ) {
if( w[e[i].x]!=w[e[i].y] ) res = res * e[i].A % mod ;
else res = res * e[i].B % mod ;
}
ans = ( ans + res ) % mod ;
}
void dfs( int x , int nw )
{
if( x == n+1 ) {
calc(nw) ;
return ;
}
// 新开
w[x] = nw+1 ;
dfs( x+1 , nw+1 ) ;
//
for(int i = 1 ; i <= nw ; i ++ ) {
w[x] = i ;
dfs(x+1,nw) ;
}
}
void solve()
{
g[0] = 1 ;
ans = 0 ;
for(int i = 1 ; i <= n ; i ++ ) g[i] = g[i-1]*(K-i+1)%mod ;
dfs(1,0) ;
printf("%lld\n" , ans ) ;
}
}
struct edg
{
int lst , to , id ;
}E[2*N] ;
int head[N] , tot = 1 ;
inline void add( int x , int y , int id )
{
E[++tot] = (edg){head[x],y,id} ;
head[x] = tot ;
}
namespace subtask2
{
const int M = 5010 ;
LL f[N][M] , Sum[N] ;
void dfs( int x , int fa )
{
for(int i = 1 ; i <= K ; i ++ ) f[x][i] = 1 ;
Sum[x] = 0 ;
for(int i = head[x] ; i ; i = E[i].lst ) {
int t = E[i].to ;
if( t == fa ) continue ;
dfs( t , x ) ;
for(int j = 1 ; j <= K ; j ++ ) {
f[x][j] = f[x][j] * (((Sum[t]-f[t][j])*e[E[i].id].A+f[t][j]*e[E[i].id].B)%mod) % mod ;
}
}
for(int i = 1 ; i <= K ; i ++ ) Sum[x] = ( Sum[x] + f[x][i] ) % mod ;
}
void solve()
{
for(int i = 1 ; i <= m ; i ++ ) {
add( e[i].x , e[i].y , i ) ;
add( e[i].y , e[i].x , i ) ;
}
dfs( 1 , 0 ) ;
printf("%lld\n" , (Sum[1]+mod)%mod ) ;
tot = 0 ;
memset( head , 0 , sizeof head ) ;
}
}
namespace subtask3
{
// emmm 似乎并不依赖返祖边的性质
const int M = 15 ;
int dfn[N] , tim , Y[M] , R , nam[N] ;
bool tree[N<<1] ;
void dfs1( int x , int inE )
{
dfn[x] = ++tim ;
for(int i = head[x] ; i ; i = E[i].lst ) {
if( i==(inE^1) ) continue ;
int t = E[i].to ;
if( !dfn[t] ) tree[i] = 1 , dfs1(t,i) ;
else {
if( dfn[t]<dfn[x] ) Y[++R] = t ;//返祖边
}
}
}
int w[M] , col ;
LL ans , f[N][M] , Sum[N] ;// 1~col 表示划分出的这几种颜色,0 表示与这些都不同
void dfs3( int x , int inE )
{
for(int i = 1 ; i <= col ; i ++ ) {
if( nam[x] ) f[x][i] = 0 ;
else f[x][i] = 1 ;
}
if( nam[x] ) f[x][w[nam[x]]] = 1 , f[x][0] = 0 ;
else f[x][0] = 1 ;
Sum[x] = 0 ;
for(int i = head[x] ; i ; i = E[i].lst ) {
if( i==(inE^1) ) continue ;
int t = E[i].to ;
if( tree[i] ) {
dfs3( t , i ) ;
for(int j = 1 ; j <= col ; j ++ ) {
f[x][j] = ((f[t][0]*(K-col)%mod+Sum[t]-f[t][j])*e[E[i].id].A+f[t][j]*e[E[i].id].B) % mod * f[x][j] % mod ;
}
f[x][0] = ((Sum[t]+f[t][0]*max(0,(K-col-1)))%mod*e[E[i].id].A+f[t][0]*e[E[i].id].B)%mod*f[x][0]%mod ;
}
else {
if( dfn[t]<dfn[x] ) {
int c = w[nam[t]] ;
for(int j = 1 ; j <= col ; j ++ ) {
if( j == c ) f[x][j] = f[x][j]*e[E[i].id].B%mod ;
else f[x][j] = f[x][j]*e[E[i].id].A%mod ;
}
f[x][0] = f[x][0]*e[E[i].id].A%mod ;
}
}
}
for(int j = 1 ; j <= col ; j ++ ) {
Sum[x] = ( Sum[x] + f[x][j] ) % mod ;
}
}
LL g[N] ;
void calc( int cnt ) // cnt 个集合
{
col = cnt ;
dfs3( 1 , 0 ) ;
for(int j = 1 ; j <= col ; j ++ ) {
ans = ( ans + f[1][j]*g[col] ) % mod ;
}
ans = ( ans + f[1][0]*(K-col)%mod*g[col] ) % mod ;
}
void dfs2( int x , int nw )
{
if( x == R+1 ) {
if( nw <= K ) calc(nw) ;
return ;
}
w[x] = nw+1 ;
dfs2( x+1 , nw+1 ) ;
for(int i = 1 ; i <= nw ; i ++ ) {
w[x] = i ;
dfs2(x+1,nw) ;
}
}
void solve()
{
for(int i = 1 ; i <= m ; i ++ ) {
add( e[i].x , e[i].y , i ) ;
add( e[i].y , e[i].x , i ) ;
}
dfs1( 1 , 0 ) ;
sort( Y+1 , Y+R+1 ) ;
R = unique( Y+1 , Y+R+1 ) - (Y+1) ;
for(int i = 1 ; i <= R ; i ++ ) nam[Y[i]] = i ;
g[0] = 1 ;
for(int i = 1 ; i <= n ; i ++ ) g[i] = g[i-1]*(K-i+1)%mod ;
dfs2( 1 , 0 ) ;
printf("%lld\n" , (ans+mod)%mod ) ;
tot = 1 ; tim = 0 ; R = 0 ; ans = 0 ;
memset( head , 0 , sizeof head ) ;
memset( dfn , 0 , sizeof dfn ) ;
memset( nam , 0 , sizeof nam ) ;
memset( tree , 0 , sizeof tree ) ;
}
}
void solve()
{
scanf("%d%d%d" , &n , &m , &K ) ;
for(int i = 1 ; i <= m ; i ++ ) {
scanf("%d%d%d%d" , &e[i].x , &e[i].y , &e[i].A , &e[i].B ) ;
}
if( n<=12 ) {
subtask1::solve() ;
return ;
}
if( m == n-1 ) {
subtask2::solve() ;
return ;
}
subtask3::solve() ;
}
int main()
{
freopen("color.in","r",stdin) ;
freopen("color.out","w",stdout) ;
int t ;
scanf("%d" , &t ) ;
while( t -- ) solve() ;
return 0 ;
}