在这片梦想之地,不堪回首的过去像泡沫一样散|

PassName

园龄:3年1个月粉丝:32关注:16

2024 1 月杂题选讲

2024 1 月杂题选讲

[集训队互测 2023] 基础寄术练习题

k=1 的情况,如果已经确定了这个序列 a,则该式意义为,将总共 n 种颜色的球,第 i 种颜色是 ai,有 ai 个,这 ai 个球排成一排,第一个球是 an 种颜色,第二种出现的颜色的球是第 an1 种颜色,第 i 种球是 ani+1 种颜色的概率,再除以 1i=1nai。在 k=1 的时候,所有排列的答案之和为 1i=1nai,在 k=2 的时候,枚举 a1 是什么,然后计算满足上面条件的排列方案数。考虑容斥,枚举一个集合在 a1 后面出现,直接 dp 即可。

signed main()
{
    cin >> n >> m >> k >> mod;
	
	inv[1] = 1;
	
	for (rint i = 2; i <= N; i++)
	{
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	}
	
	if (k == 1)
	{
		g[0] = 1;
		for (rint i = 1; i <= m; i++)
		{
			for (rint j = i - 1; ~j; j--)
			{
		   	 	g[j + 1] += inv[i] * g[j] % mod;
				g[j + 1] %= mod;		
			}	
		}
		cout << g[n] << endl;		
		return 0;
	}

  	f[0][0][0][0] = 1;
  	for (rint i = 1, p = 1, q = 0; i <= m; s += i, i++, p ^= 1, q ^= 1)
  	{
  		for (rint j = 0; j < i; j++)
  		{
  			for (rint k = 0; k <= s; k++)
  			{
  			    for (rint l = 0; l <= 1; l++)
  				{
  					int w = f[q][j][k][l];
  					f[q][j][k][l] = 0;
  					
  					f[p][j][k][l] += w;
  					f[p][j][k][l] %= mod;
  					
  					f[p][j + 1][k][l] += w * inv[i] % mod;
  					f[p][j + 1][k][l] %= mod;
  					
  					f[p][j + 1][k + i][l] += (mod - w) * inv[i] % mod;
  					f[p][j + 1][k + i][l] %= mod;
  					
  					if (!l) 
  					{
  						f[p][j + 1][k + i][1] += w * i % mod;
  						f[p][j + 1][k + i][1] %= mod;
  					}
  				}	
  			}
  		}		
  	}
  
  	for (rint i = 0; i <= s; i++)
  	{
  		ans += f[m & 1][n][i][1] * inv[i] % mod;
  		ans %= mod;
  	}
  	
  	cout << ans << endl;  
	
	return 0;
}

[国家集训队] Crash 的文明世界

考虑树形 dp 的 up and down trick,求这个柿子

j=1ndist(i,j)k

考虑树形 dp,在第一遍 up 的时候,我们设 f[i][k]jidist(i,j)k

ji 表示 ji 子树内部

f[i][k]=fa[j]=itj(dist(t,j)+1)k

二项式定理展开

f[i][k]=fa[j]=itjr=0kCkrdist(t,j)r

f[i][k]=fa[j]=ir=0kCkrtjdist(t,j)r

f[i][k]=fa[j]=ir=0kCkrf[j][r]

down 的方程式:

down 的时候 f[i][k] 表示是整棵树,到达 i 首先要到达 fa[i],于是就有 f[i][k]+=ji,jfa[i](dist(fa[i],j)+1)k

f[i][k]+=ji,jfa[i]r=0kCkrdist(fa[i],j)r

考虑继续优化,发现求 xk 可以理解为把 k 个物品放到 x 个互不相同的盒子里,允许有盒子空着不放的方案数,于是写成 xk=i=1kS(k,i)Cxii! 其中 S(k,i) 是第二类斯特林数,表示的是将 k 个球分到 i 个盒子里,这 i 个盒子没有差别,而且没有盒子是空的的方案数。

继续化柿子

ans[t]=j=1ndist(t,j)k

=j=1ni=1kS(k,i)(dist(t,j)i)i!

=i=1kS(k,i)i!j=1n(dist(t,j)i)

原问题转化为求出 j=1n(dist(t,j)i)

j=1n(dist(t,j)i)=j=1n(dist(t,j)1i1)+j=1n(dist(t,j)1i)

再用类似于上方想法的树形 dp 转移一下就可以了。

void dfs1(int x)
{
	f[x][0] = 1;
	for (rint i = h[x]; i; i = ne[i])
	{
		int y = e[i];
		if (!d[y])
		{
			d[y] = d[x] + 1;
			dfs1(y);
			f[x][0] += f[y][0];
			for (rint j = 1; j <= m; j++)
			{
				f[x][j] = (f[x][j] + f[y][j - 1] + f[y][j]) % mod;
			} 
		}	
	}

}

void dfs2(int x)
{
	for (rint i = h[x]; i; i = ne[i])
	{
		int y = e[i];
		if (d[y] > d[x])
		{
			memset(a, 0, sizeof a);
			a[0] = f[x][0] - f[y][0];
			
			for (rint j = 1; j <= m; j++)
			{
				a[j] = (f[x][j] - f[y][j] - f[y][j - 1] + mod) % mod;
			}
		            
			f[y][0] += a[0];
			
			for (rint j = 1; j <= m; j++)
			{
				f[y][j] = (f[y][j] + a[j] + a[j - 1]) % mod;
			}
			dfs2(y);	
		}
	}
}

signed main()
{
	cin >> n >> m;
	
	for (rint i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	
	s[0][0] = 1;
	
	for(rint i = 1; i <= m; i++)
	{
		s[i][1] = 1;
		s[i][i] = 1;
	}
	
	for (rint i = 1; i <= m; i++)
	{
		for (rint j = 1; j < i; j++)
		{
			s[i][j] = (s[i - 1][j - 1] + s[i - 1][j] * j) % mod;
		}
	}
			
	fac[0] = 1;
	for (rint i = 1; i <= m; i++)
	{
		fac[i] = (fac[i - 1] * i) % mod;		
	}
	d[1] = 1;
	
	dfs1(1);
	dfs2(1);
	
	for (rint i = 1; i <= n; i++)
	{
		for (rint j = 1; j <= m; j++)
		{
			f[i][j] = (f[i][j] % mod + mod) % mod;
		}
	}
	
	for (rint i = 1; i <= n; i++)
	{
		int ans = 0;
		for (rint j = 1; j <= m; j++)
		{
			ans = (ans + s[m][j] * fac[j] % mod * f[i][j]) % mod;
		}
		cout << ans << endl;
	}
	return 0;
}

[CTSC2007] 挂缀

很简单的一个反悔贪心,考虑何时为最优序列,显然有 Ci+Wi<Cj+Wj

后面的操作将已选的珠子放入一个优先队列,如果当前珠子不够加入,还比队首轻就那就将队首珠子删去。

实现的话怎么实现都行。

[WC2021] 表达式求值

做的时候人是麻的,就知道要中缀转后缀,然后就不知道怎么写了,后来学习了一下 wrpwrp 大佬的题解。

首先每一位是独立的, 互相之间不影响, 所以可以把每一位拆开考虑。

发现每次你只要考虑 10 个数, 但是 dp 复杂度依赖于操作串的长度, 考虑优化,发现所有操作都是最小最大, 只和大小有关而和具体值是多少关系不大。

枚举一个数当做答案, 然后大于等于这个数的设为 1, 小于的设为 0。然后 dp 一遍求出最后结果为 1 的方案数, 就是最后的结果大于等于当前枚举的值的结果。简单差分即可得到答案。同时由于转化成为了01 序列, 就只有 210 种情况了, 预处理一下就好了。

int built(int l, int r) 
{
    if (l == r) 
	{
        ++idx;
        val[idx] = s[l] - '0';
        return idx;
    }
    if (p[r] == l) return built(l + 1, r - 1);
    int id = ++idx, pos = p[r] - 1;
    son[0][id] = built(l, pos - 1), son[1][id] = built(pos + 1, r);
    if (s[pos] == '?') val[id] = 10;
    else if (s[pos] == '<') val[id] = 11;
    else val[id] = 12;
    return id;
}

void dp(int x) 
{
    if (val[x] < 10) 
	{
        f[x][(k >> val[x]) & 1] = 1;
        return ;
    }
    dp(son[0][x]);
	dp(son[1][x]);
    for (rint i = 0; i <= 1; i++)
    {
        if (f[son[0][x]][i])
        {
            for (rint j = 0; j < 2; j++)
            {
                if (f[son[1][x]][j]) 
				{
                    int tmp = f[son[0][x]][i] * f[son[1][x]][j] % M;
                    if (val[x] == 10)
                    {
	                    (f[x][i] += tmp) %= M;
						(f[x][j] += tmp) %= M;					
					}
                    if (val[x] == 11)
                    {
                        (f[x][min((int)i, (int)j)] += tmp) %= M;						
					}
                    if (val[x] == 12)
                    {
                        (f[x][max((int)i, (int)j)] += tmp) %= M;						
					}
                }				
			}
		}	
	}
}

signed main() 
{
    cin >> n >> m;
    for (rint i = 0; i < m; i++)
    {
		for (rint j = 1; j <= n; j++)
		{
			cin >> a[i][j];
		}
	}
    scanf("%s", s + 1);
	int leng = strlen(s + 1);
    for (rint i = 1; i <= leng; i++) 
	{
        if (s[i] == '(') stk.push((int)i);
        if (s[i] == ')') p[i] = stk.top(), stk.pop();
        if ('0' <= s[i] && s[i] <= '9') p[i] = i;
    }
    root = built(1, leng);
    for (k = 0; k < (1 << m); k++) 
	{
        memset(f, 0, sizeof(f));
        dp(root);
        g[k] = f[root][1];
    }
    for (rint i = 1; i <= n; i++) 
	{
        vector<pair<int, int> > v;
        for (rint j = 0; j < m; j++) v.push_back({a[j][i], (int)j});
        sort(v.begin(), v.end());
        int now = (1 << m) - 1, j = 0;
        (ans += v[0].x * g[now] % M) %= M;
        while(j < m) 
		{
            int x = v[j].x;
            while (j < m && v[j].x == x)
            {
                now ^= (1 << v[j].y); 
				j++;				
			}
            if (now > 0)
            {
                (ans += g[now] * (v[j].x - x) % M) %= M;				
			}
        }
    }

    cout << ans << endl;
    
    return 0;
}

[CTSC2016] 单调上升序列

很有意思的一个构造题。

把边分成 n2 个小组,对于同个中的边赋连续区间的值,限制最长单调上升序列不超过小组的数量。

现在,讲原问题转变为 :给出一张 n 个点的完全图,把边分为 n1 组完美匹配

对于所有 0xyn1,我们把 (x,y) 这条边归入编号为 x+y(modn1) 的集合。处理所有连向 n1 的边; 发现对于每个集合编号 i,有且仅有一个 k 使得 2k=i(modn1),把 k 在当前集合里连向 n1 即可。

signed main()
{
	cin >> n;
	for (rint i = 0; i <= n - 2; i++)
	{
		a[i] = i * n / 2;
	}
	
	for (rint i = 0; i <= n - 2; i++)
	{
		int k;
		for (rint j = i + 1; j <= n - 2; j++)
		{
			k = (i + j) % (n - 1);
			a[k]++;
			cout << a[k] << " ";
		}
		k = i * 2 % (n - 1);
		a[k]++;
		cout << a[k] << endl;
	}
	
    return 0;
} 

[CTS2023] 琪露诺的符卡交换

将题意转化为如何钦定每一排的顺序,使得每种颜色在每一列都出现过。

将每一排和它有的颜色连边,建出二分图,对于每一列跑一个二分图最大匹配,得出每一个颜色的位置。

对于两侧点集为 X,Y(|X|<|Y|) 的二分图, N(S) 表示点集 S 的邻域,若 maxSX(|S|N(S))0,那么存在完美匹配。

考虑在本题中,经过 i 轮删边后,每个点的度数为 ni,对于左侧任意一个大小为 x 的点集,向右侧连边数为 (ni)x。若邻域小于 x,那么一定存在一个点的度数大于 ni,不符合题意,所以一定有完美匹配。

bool dfs(int x)
{
	if (v[x]) return 0;
	v[x] = 1;
	for (rint y = 1; y <= n; y++)
	{
		if (s[x][y] != 0 && (!fa[y] || dfs(fa[y])))
		{
			fa[y] = x; return 1;
		}		
	}
	return 0;
}

signed main()
{
	int T;
	cin >> T;
	while (T--)
	{
		cin >> n;
		for (rint i = 1; i <= n; i++)
		{
			for (rint j = 1; j <= n; j++)
			{
				cin >> a[i][j];
				s[i][a[i][j]]++;
			}
		}
		for (rint j = 1; j <= n; j++)
		{
			memset(fa, 0, n + 1 << 2);
			for (rint i = 1; i <= n; i++)
			{
				memset(v, 0, n + 1);
				dfs(i);
			}
			for (rint i = 1; i <= n; i++)
			{
				int k = 1;
				while (a[fa[i]][k] != i) k++;
				a[fa[i]][k] = 0;
				b[fa[i]][j] = k;
				s[fa[i]][i]--;
			}
		}

		int ans = n * (n - 1) >> 1;
		cout << ans << endl;
		
		for (rint i = 1; i <= n; i++)
		{
			for (rint j = 1; j < i; j++)
			{
				printf("%d %d %d %d\n", i, b[i][j], j, b[j][i]);
			}				
		}
	}
	
	return 0;
} 

[清华集训2017] 榕树之心

设一个 fi 为从以 i 为根的子树开始移动最终能离 i 最近的距离

x 记为每个结点的重儿子

如果 fx+1sizeisizex1 ,从重儿子中移动,离重儿子最近的距离 +1,即为离当前结点的距离,如果此距离小于其它可以移动的结点数量,则当 sizei1mod2=0 时,则 fi=0 ,否则即 fi=1
反之,则 fi=fxsizei+sizex+2

考虑优化。遍历时维护一个 1x 缩点后的重儿子 son, son=max(sonx,son)

mson=x 时,用次重儿子进行判断。维护一个次重儿子,每个点的重儿子和次重儿子和 f 一样预处理好,当我们遍历向下一个结点拓展时讨论情况更新即可。

int cmp(int x, int y) 
{
    return size[x] > size[y] ? x : y;
}

void dfs1(int x, int father) 
{
    size[x] = 1;
	d[x] = d[father] + 1;
    for (rint i = h[x]; i; i = ne[i]) 
	{
        int y = e[i];
        if (y == father)
        {
			continue;
		}

        dfs1(y, x);
        size[x] += size[y];

        if (size[y] > size[son[x][1]]) 
		{
            son[x][2] = son[x][1];
            son[x][1] = y;
        } 
		else if (size[y] > size[son[x][2]]) 
		{
            son[x][2] = y;
        }
    }

    if (size[son[x][1]] - 2 * f[son[x][1]] <= size[x] - size[son[x][1]] - 1) 
	{
        f[x] = (size[x] - 1) / 2;
    } 
	else 
	{
        f[x] = size[x] - 1 - size[son[x][1]] + f[son[x][1]];
    }
}

void dfs2(int x, int father, int t) 
{
    int k = cmp(t, son[x][1]);

    if (size[k] - 2 * f[k] <= n - d[x] - size[k]) 
	{
        ans[x] = (n & 1) == (d[x] & 1);
    }

    for (rint i = h[x]; i; i = ne[i]) 
	{
        int y = e[i];
        if (y == father)
        {
			continue;
		}
        dfs2(y, x, y == son[x][1] ? cmp(son[x][2], t) : cmp(son[x][1], t));
    }
}

signed main() 
{
    cin >> w >> T;
    while (T--) 
	{
        idx = 0;m(f);m(h);m(e);m(ne);m(son);m(size);m(ans);
	    cin >> n;
	    for (rint i = 1; i < n; i++) 
		{
	        int a, b;
	        cin >> a >> b;
	        add(a, b);
	        add(b, a);
	    }
	
	    dfs1(1, 0);
		dfs2(1, 0, 0);
	
	    if (w == 3)
	    {
			cout << ans[1] << endl;
		}
	    else 
		{
	        for (rint i = 1; i <= n; i++)
	        {
				cout << ans[i];
			}
	        cout << endl;
	    }
    }

    return 0;
}

[清华集训2016] 如何优雅地求和

学习了一手 Binomial Sum 的微积分做法,很新颖。

i=0mai[xi]G(x)k,k[1,n]

然后求 i=0mai[xi]F(G(x))

构造 G(x)=ex,F(x)=(ax+1a)n

(ax+1a)F(x)anF(x)=0

G(0)=1,设 F(x)=F(x)mod(x1)m+1,所以 F(x+1)=F(x+1)modxm+1=(ax+1)nmodxm+1

变为 (ax+1)F(x+1)anF(x+1)=0

考察 (ax+1)F(x+1)anF(x+1) 其不合法的项,只有 F 从其原函数的 m+1 次跨向 m 次中来

所以 (ax+1)F(x+1),具体地 1F(x+1) 应该获得贡献。但是实际上不存在。所以要在右边减去。

右边等于 xm[xm]F(x+1)=nam+1(n1m)xm

发现右边是一个可以变化、展开的项,令 x1x

然后得到 (ax+1a)F(x)anF(x)=nam+1(n1m)(x1)m

[xi]F(x)=fi[xi] 左式 =(1a)(i+1)fi+1+aifianfi

同时 [xi] 右式 =nam+1(n1m)(mi)(1)mi

由于两边多项式相等,系数也相等,左式等于右式,得出 fi 递推式。

fm+1=0,倒着推更优。

卡老师还有一个神奇做法,见 link

int qpow(int a, int b)
{
    int s = 1;
    while (b)
	{
        if(b & 1) s = s * a % mod;  
        a = a * a % mod, b >>= 1;       
    }
    return s % mod;
}

int power(int x){return x % 2 ? mod - 1 : 1;}
int subm(int x){return (x % mod + mod) % mod;}
int C(int a, int b){return fac[a] * ifac[b] % mod * ifac[a - b] % mod;}

void init()
{
    fac[0] = 1;
    for (rint i = 1; i <= m; i++)
    {
        fac[i] = fac[i - 1] * i % mod;		
	}
    ifac[m] = qpow(fac[m], mod - 2);

    for (rint i = m - 1; i >= 0; i--)
    {
        ifac[i] = ifac[i + 1] * (i + 1) % mod;		
	}	
}

signed main() 
{
    cin >> n >> m >> a;

    for (rint i = 0; i <= m; i++)
    {
		cin >> g[i];
	}

    init();

    int res = 0;
    if (n <= m) 
	{
        for (rint i = 0; i <= n; i++) 
		{
            res += g[i] * C(n, i) % mod * qpow(a, i) % mod * qpow(mod + 1 - a, n - i) % mod;
            res %= mod;
        }
        cout << res << endl;
        return 0;
    }

    int k = ifac[m];
	int p = qpow(a, m + 1); 

    for (rint i = 1; i <= m; i++) 
	{
        k = k * (n - i) % mod;
    }
    f[m + 1] = 0;
    for (rint i = m; i >= 0; i--) 
	{
        f[i] = qpow(a * (n - i) % mod, mod - 2) * subm((i + 1) * (mod + 1 - a) % mod * f[i + 1] % mod - n * p % mod * k % mod * C(m, i) % mod * power(m - i + 1) % mod) % mod;
    }

    for (rint i = 0; i <= m; i++) 
	{
        res += g[i] * f[i] % mod;
        res %= mod;
    }

    cout << res << endl;
    
    return 0;
}

[清华集训2016] 组合数问题

卢卡斯的过程,实际上就是把最后一位数单独拿出来算,然后把最后一位去掉。把两个位置上的数都拆分的话,那么就相当于每一个位置上的组合数乘起来。
Ca,b=ia,jbCi,j

原问题变为,有多少个 in,jm 的数对 (i,j),满足在 K 进制拆分下的某一位数 i<j

数位 dp 设 fi,01,01,01 表示当前在 i 位,是否满足要求,i 的填法是否危险,j 的填法是否危险。记忆化转移即可。

int dfs(int pos, bool c, bool flag, bool t1, bool t2) 
{
    if (!pos) return c;
    auto &res = f[pos][c][flag][t1][t2];
    if (res != -1) return res;
    res = 0;
    int d = t1 ? a[pos] : k - 1, q = t2 ? b[pos] : k - 1;

    for (rint i = 0; i <= d; i++)
    {
        for (rint j = 0; (j <= i || flag) && j <= q; j++) 
		{
            bool a = c, b = flag;
            a |= (i < j), b |= (i != j);
            (res += dfs(pos - 1, a, b, t1 && i == d, t2 && j == q)) %= mod;
        }		
	}

    return res;
}

int solve(int n, int m) 
{
    int len = 0, lem = 0;
    memset(a, 0, sizeof a);
	memset(b, 0, sizeof b);
    memset(f, -1, sizeof f);

    while (n) a[++len] = n % k, n /= k;
    while (m) b[++lem] = m % k, m /= k;

    return dfs(max(len, lem), 0, 0, 1, 1);
}

signed main() 
{
    int T;
    cin >> T >> k;
    while (T--) 
	{
        int a, b;
        cin >> a >> b;
        cout << solve(a, b) << endl;
    }

    return 0;
}

[清华集训2015] 灯泡

简单数学题,也可以三分,做法很多,而且实际上不卡 long double

如果墙上有影子,那么 D+H((Hh)/tanθ+Dtanθ)

如果 (Hh)/DH/D,答案为 hD/H

如果 (Hh)/D(Hh)/D,答案为 h

否则答案为 D+H2×(Hh)×D

[清华集训 2017] 福若格斯

可以通过这个题学习不平等博弈,由于整理时间较长,直接给自己留个链接算了,以后回来看看 Arahc's blog

int qpow(int a, int b)
{
    int s = 1;
    while (b)
	{
        if(b & 1) s = s * a % mod;  
        a = a * a % mod, b >>= 1;       
    }
    return s % mod;
}

int C(int x, int y) 
{
    if (y < 0 || x < y) return 0;
    return fac[x] * ifac[y] % mod * ifac[x - y] % mod;
}

int calc(int x, int y, int z) {return C(x + y, y + z);}

void solve() 
{
    int star = 0, up = 0, dwn = 0, c0 = 0, c1 = 0, f1 = 0, c2 = 0, f2 = 0, n;
    cin >> n;

    for (rint i = 1; i <= n; i++) 
	{
        int x;
		string s;
        cin >> s >> x;

        if (s == "LL_RR" || s == "LRL_R" || s == "L_RLR")  star += x;
        else if (s == "LRRL_" || s == "RLRL_" || s == "RRL_L") c1 += x;
        else if (s == "_RLRL" || s == "_RLLR" || s == "R_RLL") f1 += x;
        else if (s == "RL_LR") c2 += x;
        else if (s == "LR_RL") f2 += x;
        else if (s == "L_LRR") up += x;
        else if (s == "LLR_R") dwn += x;
        else c0 += x;
    }

    int wL = 0, wR = 0, wfir = 0, wsec = 0, w0 = 0;
    int all = qpow(2, c1 + f1);
    int sumL = 0, sumR = 0;

    for (rint i = -f1; i <= c1; i++)
    {
        pre[i + f1] = (i == -f1 ? 0 + calc(c1, f1, i) : pre[i - 1 + f1] + calc(c1, f1, i)) % mod;	
	}
    for (rint i = c1; i >= -f1; i--)
    {
        suf[i + f1] = (i == c1 ? 0 + calc(c1, f1, i) : suf[i + 1 + f1] + calc(c1, f1, i)) % mod;		
	}

    for (rint i = -f2; i <= c2; i++) 
	{
        int val = calc(c2, f2, i), c0 = 0;
        int pL = -i / 2, pR = -i / 2;

        while (pL * 2 + i <= 0) pL++;
        while ((pL - 1) * 2 + i > 0) pL--;
        while (pR * 2 + i >= 0) pR--;
        while ((pR + 1) * 2 + i < 0) pR++;
        sumL = pL > c1 ? 0 : suf[max(0ll, pL + f1)];
        sumR = pR < -f1 ? 0 : pre[min(c1 + f1, pR + f1)];
        if (i % 2 == 0) c0 = calc(c1, f1, -i / 2);
        if (!val) continue;
        wL = (wL + val * (sumL + mod)) % mod;
        wR = (wR + val * (sumR + mod)) % mod;
        w0 = (w0 + val * c0) % mod;
    }

    int sstar = (star == 0 ? 1 : qpow(2, star - 1));
    int suf = 0, pre = 0;

    for (rint i = up; i > 1; i--)
    {
        suf = (suf + calc(up, dwn, i)) % mod;		
	}

    for (rint i = -dwn; i < -1; i++)
    {
        pre = (pre + calc(up, dwn, i)) % mod;	
	}

    int g1 = calc(up, dwn, 1);
	int g_1 = calc(up, dwn, -1);
	int g0 = calc(up, dwn, 0);
    
	all = qpow(2, up + dwn + star);
    wL = (wL * all % mod + sstar * w0 % mod * (g1 + suf)) % mod;
    wR = (wR * all % mod + sstar * w0 % mod * (g_1 + pre)) % mod;
    wsec = (wsec + w0 * sstar % mod * g0) % mod;

    sstar = (star == 0 ? 0 : qpow(2, star - 1));
    wL = (wL + sstar * w0 % mod * suf) % mod;
    wR = (wR + sstar * w0 % mod * pre) % mod;
    wfir = (wfir + (g0 + g1 + g_1) % mod * sstar % mod * w0) % mod;

    wL = wL * qpow(2, c0) % mod;
    wR = wR * qpow(2, c0) % mod;
    wfir = wfir * qpow(2, c0) % mod;
    wsec = wsec * qpow(2, c0) % mod;

    wL = (wL + mod) % mod;
    wR = (wR + mod) % mod;
    wfir = (wfir + mod) % mod;
    wsec = (wsec + mod) % mod;
    cout << wL << " " << wR << " " << wfir << " " << wsec << endl;
}

signed main() 
{
    fac[0] = ifac[0] = 1;

    for (rint i = 1; i <= N; i++)
    {
        fac[i] = fac[i - 1] * i % mod;		
	}

    ifac[N] = qpow(fac[N], mod - 2);

    for (rint i = N - 1; i >= 1; i--)
    {
        ifac[i] = ifac[i + 1] * (i + 1) % mod;		
	}

    cin >> num >> T;

    while (T--)
    {
        solve();		
	}

    return 0;
}

CF1801E Gasoline prices

fi,j 表示从点 i 向祖先方向的前 2j 个点构成的链,gi,j 表示从点 i 向祖先方向的前 2j 个点构成的链翻转后的结果

j 层的并查集中包含 fk,jgk,j 中的所有元素,从 12n 编号。对于一个限制,求两条链上的点的值一一对应相等,在并查集中直接合并,以两条链的 LCA 为分界分成 3 部分,其中一部分是把一个 fk,k 和一个 gk,k 合并,还有两部分是把两个 f 合并。并查集中的每一层最多被合并 2n 次,所以总复杂度仍然是O(nlog2n)

#include <bits/stdc++.h>
 
#define rint register int
#define int long long
#define endl '\n'
 
using namespace std;
 
const int N = 2e5 + 5;
const int M = 4e5 + 5;
const int mod = 1e9 + 7;
 
int n, q, fa[N][25], l[N], r[N];
int dep[N];
int ans = 1;
int h[N], e[N], ne[N], idx;
 
int qpow(int a, int b)
{
    int res = 1;
    while (b)
	{
        if (b & 1) res = res * a % mod;  
        a = a * a % mod;        
        b >>= 1;       
    }
    return res;
}
 
void add(int a, int b)
{
	e[++idx] = b, ne[idx] = h[a], h[a] = idx;
}
 
void dfs(int x, int depth) 
{
    dep[x] = depth;
    for (rint i = h[x]; i; i = ne[i])
    {
		int y = e[i];
		dfs(y, depth + 1);
	}
}
 
int lca(int x, int y) 
{
    for (rint i = 18; i >= 0; i--)
        if (fa[x][i] > 0 && dep[fa[x][i]] >= dep[y])
            x = fa[x][i];
    for (rint i = 18; i >= 0; i--)
        if (fa[y][i] > 0 && dep[fa[y][i]] >= dep[x])
            y = fa[y][i];
    if (x == y) return x;
    for (rint i = 18; i >= 0; i--)
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}
 
int get(int x, int y) 
{
    for (rint i = 0; i <= 18; i++) if (y & (1 << i)) x = fa[x][i];
    return x;
}
 
struct dsu 
{
    int fa[M];
    dsu() 
	{
        for (rint i = 0; i < M; i++) fa[i] = i;
    }
    int find(int x) 
    {
        return fa[x] == x ? x : fa[x] = find(fa[x]);
    }
    bool merge(int x, int y, int p) 
	{
        if (find(x) == find(y)) return 0;
        if (!p) 
		{
            ans *= qpow((r[fa[x]] - l[fa[x]] + 1) * (r[fa[y]] - l[fa[y]] + 1) % mod, mod - 2);
            ans %= mod;
            ans *= max(min(r[fa[x]], r[fa[y]]) - max(l[fa[x]], l[fa[y]]) + 1, 0ll);
            ans %= mod;
            l[fa[y]] = max(l[fa[y]], l[fa[x]]);
			r[fa[y]] = min(r[fa[y]], r[fa[x]]);
        }
        fa[find(x)] = find(y);
        return 1;
    }
} s[25];
 
void merge(int x, int y, int p) 
{
    if (!s[p].merge(x, y, p) || !p)return;
    merge(x, y, p - 1);
    merge(fa[x][p - 1], fa[y][p - 1], p - 1);
}
 
void merge_path(int x, int y, int p) 
{
    int v = __lg(p);
    merge(x, y, v);
    merge(get(x, p - (1 << v)), get(y, p - (1 << v)), v);
}
 
void mergeR(int x, int y, int p) 
{
    if (!s[p].merge(x, y + (p == 0 ? 0 : n), p) || !p) return;
    mergeR(x, fa[y][p - 1], p - 1);
    mergeR(fa[x][p - 1], y, p - 1);
}
 
void merge_pathR(int x, int y, int p) 
{
    int v = __lg(p);
    mergeR(x, get(y, p - (1 << v)), v);
    mergeR(get(x, p - (1 << v)), y, v);
}
 
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = x * 10 + ch - '0', ch = getchar();
    return x * f;
}
 
signed main() 
{
    cin.tie(0);
	cout.tie(0);
	
	cin >> n;
 
    for (rint i = 2; i <= n; i++)
    {
		fa[i][0] = read();
		add(fa[i][0], i);
	}
 
    for (rint i = 0; i < 19; i++) 
	{
		for (rint j = 1; j <= n; j++) 
		{
			fa[j][i + 1] = fa[fa[j][i]][i];
		}
	}
	
    for (rint i = 1; i <= n; i++)
	{
		l[i] = read();
		r[i] = read();
		ans *= (r[i] - l[i] + 1);
		ans %= mod;
	} 
	
    dfs(1, 0);
 
    cin >> q;
    while (q--) 
	{
        int a = read(), b = read(), c = read(), d = read();
 
        if (!ans) 
		{
            cout << 0 << endl;
            continue;
        }
 
        int p = lca(a, b);
        int q = lca(c, d);
 
        if (dep[a] - dep[p] < dep[c] - dep[q])
        {
            swap(a, c);
			swap(b, d);
			swap(p, q);			
		}
        
		int l1 = dep[c] - dep[q] + 1;
        int l2 = (dep[a] - dep[p]) - (dep[c] - dep[q]);
		int l3 = dep[a] + dep[b] - dep[p] * 2 + 1 - l1 - l2;
 
        if (l1) merge_path(a, c, l1);
        if (l2) merge_pathR(get(a, l1), get(d, l3), l2);
        if (l3) merge_path(b, d, l3);
 
        cout << ans << endl;
    }
 
    return 0;
}

[集训队互测 2023] 优惠购物

先想象这个题有没有一些性质,如果一次性使用了 c 个金币,那么在更前面使用更优。对于每个数最上面的 Axmodc 个金币,优惠券越前面用越好。思路大概有了,应该是个思维题,纯贪心有可能莽过去。

从前往后考虑每个物品,并优先使用优惠券。反悔贪心,假设对 i 使用 C_i 个优惠券,最上面 Aimodc 个优惠券不会被用金币反悔,因为一个金币换一个优惠券不优。中间有若干段整段的 c 个优惠券,如果用 c 个金币反悔,会得到 c+1 个优惠券。最后有一个非整段的优惠券,同样的,如果用金币去完全兑换,可以多得到一张优惠券,否则只能得到金币对应数量的优惠券。

对于一个物品,如果有可以反悔的一段优惠券,肯定优先使用金币去反悔这段优惠券,而不是直接用金币去购买这段商品。考虑需要的优惠券不足以兑换一整段之前的优惠券的情况,这时需要考虑到底是将前面的一段优惠券兑换一部分,还是将金币留给自己,这个可以根据留给自己是否存在一张多余优惠券,以及两者的大小关系来判断。用堆实现,时间复杂度 O(nlogn)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

#define x first
#define y second

using namespace std;

const int N = 1e6 + 5;

int n, m, c;
int a[N], b[N];

signed main() 
{
	cin.tie(0);
	cout.tie(0);    
    int T;
    cin >> T;
    
    while (T--)
    {
	    cin >> n >> m >> c;
	
	    for (rint i = 1; i <= n; i++) cin >> a[i];
	    for (rint i = 1; i <= n; i++) cin >> b[i];
	
	    priority_queue<pair<int, int> > q;
	    int ans = 0;
	    for (rint i = 1; i <= n; i++) 
		{
	        while (!q.empty() && b[i] > m) 
			{
	            auto p = q.top();
	            q.pop();
	            p.x *= -1; 
	            int w = (b[i] - m + 1) / (p.x + 1);
	            if (!w) 
				{
	                if (c - (a[i] - b[i]) % c < p.x && a[i] / c != (a[i] - b[i]) / c) 
					{
	                    q.emplace(-p.x, p.y);
	                    if (c - (a[i] - b[i]) % c <= b[i] - m)
						{
	                        int w = c - (a[i] - b[i]) % c;
	                        b[i] -= w;
	                        continue;
	                    }
	                    break;
	                }
	                ans += b[i] - m;
	                q.emplace(-(p.x - (b[i] - m)), 1);
	                p.y--;
	                if (p.y) q.emplace(-p.x, p.y);
	                m = b[i];
	                break;
	            }
	            if (w >= p.y) 
				{
	                ans += p.x * p.y;
	                m += (p.x + 1) * p.y;
	                continue;
	            }
	            ans += w * p.x;
	            m += w * (p.x + 1);
	            if (w ^ p.y) q.emplace(-p.x, p.y - w);
	        }
	        int w = min(b[i], m);
	        if (w <= a[i] % c) 
			{
	            ans += a[i] - w;
	            m -= w;
	            m += (a[i] - w) / c;
	        } 
			else 
			{
	            ans += a[i] - w;
	            m -= w;
	            m += (a[i] - w) / c;
	            w -= a[i] % c;
	
	            if (w % c) q.emplace(-(w % c), 1);
	            if (w / c) q.emplace(-c, w / c);
	        }
	    }
	    cout << ans << endl;	
	}
        
    return 0;    
}

[集训队互测 2023]Permutation Counting 2

钦定两维分别有 x,y<,最后的答案可以二项式反演得出。有 x,y< 等价于有 nx,ny 个连续段,转化成钦定有 x,y 个连续段。

考虑排列与其逆排列连续段之间的关系,发现当在放原排列的一个连续段的时候,其会在逆排列的若干个连续段最后加上若干个数。设 ai,j 表示在放原排列第 i 个连续段的时候在逆排列第 j 个连续段最后放了 ai,j 个数,则满足如下条件的 a 唯一对应一个排列:

  • ai,j=n
  • a 每行每列都有值。

前一个限制可以插板法做,后一个限制可以钦定某几行某几列为空,然后容斥,同样可以拆开成两维分别来一遍,时间复杂度 O(n3)

int n, m, k, p;
int ans[N][N], c[N][N];
int fac[M], ifac[M];
int f[N][N];

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = res * a % p;
		b >>= 1;
		a = a * a % p;
	}
	return res;
}

void init()
{
    fac[0] = ifac[0] = 1;
    for (rint i = 1; i < M; i ++)
    {
        fac[i] = fac[i - 1] * i % p;
        ifac[i] = ifac[i - 1] * qpow(i, p - 2) % p; 
    }	
}

int C(int a, int b)
{
	if (b < 0 || a < b) return 0;
	return fac[a] * ifac[a - b] % p * ifac[b] % p;
}

signed main() 
{
    cin >> n >> p;

    init();
    
    for (rint i = 0; i <= n; i++) 
	{
	    c[i][0] = 1;
		for (rint j = 1; j <= i; j++)
	    {
	        c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % p;			
		}
	}

    for (rint i = 1; i <= n; i++) 
	{
        for (rint j = 1; j <= n; j++)
        {
            f[i][j] = C(i * j + n - 1, i * j - 1);			
		}
    }

    for (rint i = 1; i <= n; i++) 
	{
        for (rint j = 1; j <= n; j++)
        {
            for (rint k = 1; k < i; k++)
            {
                f[i][j] = (f[i][j] + (p - f[k][j]) * c[i][k]) % p;					
			}	
		}
    }

    for (rint i = 1; i <= n; i++) 
	{
        for (rint j = 1; j <= n; j++)
        {
            for (rint k = 1; k < j; k++)
            {
                f[i][j] = (f[i][j] + (p - f[i][k]) * c[j][k]) % p;							
			}
		}
    }

    for (rint i = 1; i <= n; i++)
    {
        for (rint j = 1; j <= n; j++)
        {
            ans[n - i][n - j] = f[i][j];			
		}	
	}

    for (rint i = 0; i < n; i++) 
	{
        for (rint j = n - 1; ~j; j--)
        {
            for (rint k = j + 1; k < n; k++)
            {
                ans[i][j] = (ans[i][j] + (p - ans[i][k]) * c[k][j]) % p;				
			}			
		}
    }

    for (rint i = n - 1; ~i; i--) 
	{
        for (rint j = 0; j < n; j++)
        {
            for (rint k = i + 1; k < n; k++)
            {
                ans[i][j] = (ans[i][j] + (p - ans[k][j]) * c[k][i]) % p;					
			}	
		}
    }

    for (rint i = 0; i < n; i++)
    {
        for (rint j = 0; j < n; j++)
        {
            printf("%lld%c", ans[i][j], " \n"[j == n - 1]);				
		}
	}

    return 0;
}

[THUPC 2024 初赛] 排序大师

考虑在排列开头加上 0,结尾加上 n+1,然后建边 pi+1pi+1,最终状态是 n+1 个环。

假设我们现在操作 i,j,k,l,那么原来的边是 pi1+1pipj+1pj+1pk1+1pkpl+1pl+1。 现在的边是 pi1+1pk,pj+1pl+1,pk1+1pi,pl+1pj+1。 这样的操作的任意性不会强于两次选择两个点,交换其出边。选择两个点交换出边只会增加至多 1 个环,所以,如果能每次减少两个环,那么这个交换次数就是顶到下界的。

构造方法:

  • 选择最小的点 x,满足 x 前面存在一个 y>x,选择这个 y
  • 容易说明,x1y 前面,y+1x 后面,因此将 x 换到 x1 前面,y 换到 y+1 后面即可增加两个自环。

时间复杂度 O(n2)

int n, f[N], w[N];
vector<tuple<int, int, int, int> > ans;
void solve(int a, int b, int c, int d) 
{
    memset(w, 0, sizeof w);
    int idx = -1;
    for (rint i = 0; i < a; i++)          w[++idx] = f[i];
    for (rint i = c; i <= d; i++)         w[++idx] = f[i];
    for (rint i = b + 1; i < c; i++)      w[++idx] = f[i];
    for (rint i = a; i <= b; i++)         w[++idx] = f[i];
    for (rint i = d + 1; i <= n + 1; i++) w[++idx] = f[i];
    memcpy(f, w, sizeof f);
    ans.emplace_back(a, b, c, d);
}

signed main() 
{
    cin >> n;
    for (rint i = 1; i <= n; i++)
    {
		cin >> f[i];
	}
    f[0] = 0;
    f[n + 1] = n + 1;
    
    while (1) 
	{
        int maxx = 0, minn = inf;
        for (rint i = 1; i <= n; i++) 
		{
            maxx = max(maxx, f[i]);
            if (maxx > f[i]) minn = min(minn, f[i]);
        }
        if (minn == inf) break;
        int p = find(f + 1, f + n + 1, minn) - f;
		int q = max_element(f + 1, f + p + 1) - f;
        int k1 = find(f, f + n + 2, f[p] - 1) - f;
		int k2 = find(f, f + n + 2, f[q] + 1) - f;
        solve(k1 + 1, q, p, k2 - 1);
    }

    cout << ans.size() << endl;
    for (auto i : ans) 
	{
        int a, b, c, d;
        tie(a, b, c, d) = i;
        cout << a << " " << b << " " << c << " " << d << endl;
    }
    return 0;
}

CF1305G Kuroni and Antihype

将原问题转化,添加一个点权为 0 的点,将边权变为两端点点权和,将 ans 减去 ai。原题意的生成有向森林转化为了生成树。

从大到小枚举边权和 x,枚举 x 的子集 i,将点权为 iibitxorx 的点缩点并计算贡献。

有点卡常,但是不影响过。

const int N = 3e5 + 5;

int n, m;
int maxx;
int fa[N], cnt[N];
long long ans;

int find(int x) 
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int a, int b, long long w) 
{
    if (cnt[a] && cnt[b]) 
	{
        int x = find(a);
		int y = find(b);
        if (x != y) 
		{
            ans += w * (cnt[x] + cnt[y] - 1);
            fa[x] = y;
			cnt[y] = 1;
        }
    }
}

signed main() 
{
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n;
	cnt[0] = 1;

    for (rint i = 1; i <= n; i++) 
	{
        int x;
        cin >> x;
        cnt[x]++;
        ans -= x;
        maxx = max(maxx, x);
    }

    m = 1 << (32 - __builtin_clz(maxx));

    for (rint i = 0; i < m; i++)
    {
		fa[i] = i;
	}

    for (rint i = m - 1; ~i; i--) 
	{
        for (rint j = i; j; j = i & (j - 1))
        {
            if (cnt[j] && cnt[i ^ j])
            {
                merge(j, i ^ j, i);					
			}	
		}
        merge(i, 0, i);
    }

    cout << ans << endl;
    
    return 0;
}

[集训队互测 2023] Grievous Lady

由于最终结果选 a 的个数和选 b 的个数不会差很多,所以可以一开始的时候枚举 S 集合大小 cnt[n240,n2+40] 之间。然后随机排列,将前 cnt 个选 a,后 ncnt 个选 b,之后随机选数翻转,如果更优就翻。类似还有好多奇怪的随机化可以过。

当然,这道题还存在一种正解的 dp 做法,fi,j 表示选到第 i 个点,a 的总和为 j 的最大 b 总和,之后再二维偏序,分析可以被扔掉的点可以做到只剩下 O(n) 个状态。但是这个写法虽然比较稳定,但是太难写了,比较麻烦。随机化的得分会在 98100pts 间波动。但是随机种子给的好可以稳过。(直接 srand(time(0)) 过的概率也是很高的。

int n;
int A, B;
int a[N], b[N], p[N];
__int128 res;
bool v[N];

#define iint __int128
inline void write(iint n)
{
    if (n < 0)
	{
        putchar('-');
        n *= -1;
    }
    if (n > 9) write(n / 10);
    putchar(n % 10 + '0');
}
#undef iint

void solve() 
{
    __int128 l = 0, r = 0;
    
    for (rint i = 1; i <= n; i++)
    {
		if (!v[i]) l += a[i];
		else r += b[i];
	}
    __int128 now = l * r;
    int times = 4e4 + 5;
	while (times--) 
	{
        int x = rand() % n + 1;
        if (!v[x]) 
		{
            if (now < ((l - a[x]) * (r + b[x])))
            {
                v[x] ^= 1;
				l -= a[x]; 
				r += b[x]; 
				now = l * r;				
			}
        } 
		else 
		{
            if (now < (l + a[x]) * (r - b[x]))
            {
                v[x] ^= 1;
				l += a[x]; 
				r -= b[x];
				now = l * r;				
			} 
        }
    }
    res = max(res, now);
}

void work() 
{
    for (rint i = 1; i <= n; i++)
    {
		cin >> a[i] >> b[i];
		p[i] = i;
	}
    res = 0;
    int times = 30;
    while (times--)
	{
        random_shuffle(p + 1, p + n + 1);
        for (rint i = 1; i <= n / 2; i++)
        {
			v[p[i]] = 1;
		}
		for (rint i = n / 2 + 1; i <= n; i++)
		{
			v[p[i]] = 0;
		}
        solve();
    }
    write(res);
    cout << endl;
}

signed main() 
{
    srand(time(0));
    int T;
    cin >> T >> n >> A >> B;
    while (T--) work();
    return 0;
}

本文作者:PassName

本文链接:https://www.cnblogs.com/spaceswalker/p/17946064

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   PassName  阅读(48)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起