260824-260830 总结复盘
Title:260824-260830 总结复盘
Day 1:
省选模拟赛,T1为有限制的排列问题,好题,挣扎许久后AC
题目大意:从\(n\)个意象中选出\(m\)个(按顺序)组成诗,第\(i\)个意象必须是\(b[i]\)的倍数。
考虑:如果没有“第\(i\)个必须是\(b[i]\)的倍数”限制,则直接用排列数公式解决。
那么考虑容斥原理:全部的-2个重复的+3个重复的-…………以此类推。
可以得到容斥公式:
\((−1)^{∣T∣−1}\) :容斥系数
\((∣T∣−1)!\) :组合系数
\(I[T]\) : 钦定重复T个可选择意象数
\(F[S∖T]\) : 剩余部分组合数
另外,对于“某个位置是否可选”这样的问题,用二进制压位存储表示十分实用。
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1000000007;
long long a[1000005];
long long b[20];
int cnt[1<<17+5];
int fact[20];
int w[1<<17+5];
int F[1<<17+5];
int main()
{
freopen("bard.in","r",stdin);
freopen("bard.out","w",stdout);
int n, m;
cin >> n >> m;
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
for(int i = 0; i < m; i++)
{
cin >> b[i];
}
int total = 1 << m;
for(int i = 0; i < n; i++)
{
int mask = 0;
for(int j = 0; j < m; j++)
{
if(a[i] % b[j] == 0)
{
mask = mask | (1 << j);
}
}
cnt[mask] = cnt[mask] + 1;
}
for(int j = 0; j < m; j++)
{
int bit = 1 << j;
for(int mask = 0; mask < total; mask++)
{
if((mask & bit) == 0)
{
cnt[mask] = cnt[mask] + cnt[mask | bit];
}
}
}
fact[0] = 1;
for(int i = 1; i <= m; i++)
{
fact[i] = (long long)fact[i - 1] * i % MOD;
}
for(int mask = 1; mask < total; mask++)
{
int pc = __builtin_popcount((unsigned)mask);
int val = (long long)cnt[mask] * fact[pc - 1] % MOD;
if((pc & 1) == 0)
{
val = (MOD - val) % MOD;
}
w[mask] = val;
}
F[0] = 1;
for(int S = 1; S < total; S++)
{
int p = S & (-S);
int R = S ^ p;
long long sum = 0;
int sub = R;
while(1)
{
int T = sub | p;
sum = sum + (long long)w[T] * F[S ^ T] % MOD;
if(sum >= MOD)
{
sum = sum - MOD;
}
if(sub == 0)
{
break;
}
sub = (sub - 1) & R;
}
F[S] = (int)sum;
}
cout << F[total - 1] << '\n';
return 0;
}
下午虚树,这里梳理一下我的见解:
由于树上节点很多,但是真正需要查询处理的节点很少,因此考虑将需要的节点及其LCA存储在一个“虚拟的树”即虚数中,保留原树的逻辑结构,这样就可以做到快速的查询。
换言之,虚树是一种加速树上查询操作的工具。
一般用排序+LCA做法构建虚树:
- 将关键点按DFS序从小到大排序
- 遍历相邻关键点对,对每对求LCA,加入序列A
- 将所有关键点和LCA合并到序列A,按DFS序排序并去重
- 枚举相邻两点x, y,连虚树边 LCA(x,y) → y
理由:LCA具有传递性,因此按照此种操作可以保留树的父子结构。
然后,直接在虚树上开展树上DP、查询等操作即可。
Day 2:
ACM比赛,A题人类智慧+分类讨论,B题构造,D题广搜,很快解决。
整体难度较易,得分5.18分。
Day 3:
上午模拟赛,T1简单DP,值得注意的是用二进制存储每一位的奇偶以加速;
重点回顾T2:
形式化题意:
我们有一个全集\(U\),大小为\(m\),有\(n\)个子集
$ S_1, S_2, \dots, S_n \subseteq U $ ,每个元素在同一个$ S_i $中至多出现一次。
我们需要选择一个子集$ P \subseteq U $。
对于每个 $ i $,定义
若\(b_i = 1\),则称“愿望$i $被实现”。
所有被实现的愿望的下标按升序排列为$a_1 < a_2 < \dots < a_k $,我们定义比较方式为:
比较序列
的字典序,我们要使这个序列字典序最小。
然后需要:
- 对于每个\(i\),输出在最优情况下,愿望$ i $是否被实现(Yes/No)。
- 输出能达到这个最优字典序的不同$ P $ 的个数,对 $ 998244353 $ 取模。
显然,对于“字典序最小”的要求,好的贪心策略是从第一个开始,逐个查询愿望能否加入选择集合中,一旦不能就停止查询输出结果。
对于每一个愿望,由于其元素必须在集合\(U\)中出现奇数次,因此考虑对于每个愿望构造一个方程:
其中\(P_i\)表示是否选择第\(i\)个元素,\(S_i\)表示第\(i\)个元素是否属于当前愿望。
不难发现,这样的方程可以用高斯消元解决。
因为mod 2意义下的加法不会进位产生2,显然用异或解决(异或是不进位的二进制加法)
因此,对于每一个愿望,按顺序加入高斯消元矩阵,判断是否有唯一解即可。
代码:
#include <bits/stdc++.h>
using namespace std;
char *p1, *p2, buf[1 << 20];
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++)
template <typename T> void read(T &x) {
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) f = (ch == '-') ? -1 : f, ch = getchar();
while (isdigit(ch)) x = x * 10 + (ch & 15), ch = getchar(); x *= f;
}
const int mod=998244353,N=4005;
int n,m;
bitset<N> a[N];
bool ans[4005];
int insert(bitset<N>cur)
{
for(int i=0;i<m;i++)
{
if(cur[i])
{
if(a[i][i])
{
cur^=a[i];
}
else
{
a[i]=cur;
for(int j=0;j<m;j++)
{
if(j!=i&&a[j][i])a[j]^=a[i];
}
return 2;
}
}
}
if(cur[m])
{
return 0;
}
else
{
return 1;
}
}
int main()
{
freopen("hollow.in","r",stdin);
freopen("hollow.out","w",stdout);
read(m), read(n);
int tot=0;
for(int i=0;i<n;i++)
{
int c;
read(c);
bitset<N>cur;
cur[m]=1;
for(int j=0;j<c;j++)
{
int x;
read(x);
cur[x-1]=1;
}
int result=insert(cur);
if(result>0)
{
ans[i]=1;
if(result==2)tot++;
}
else
{
ans[i]=0;
}
}
long long answer=1;
for(int i=0;i<m-tot;i++)
{
answer=answer*2%mod;
}
for(int i=0;i<n;i++)
{
printf(ans[i]?"Yes\n":"No\n");
}
printf("%lld",answer%mod);
return 0;
}
Day 4:
四象俱全,惨不忍睹 \(qwq\)

言归正传,T1为CSP-S 2024T1升级版,考虑用攻击力刚好大于该怪兽的第一个怪兽击杀当前怪兽,这样就不会浪费高攻击力的怪兽了。题很简单,就不放代码了。
T2数据范围可疑(\(n≤10^9\)),情况复杂多样,显然并非普通DP。该题有很大的探究价值,择日专列一篇blog复盘,此处不再展开。
下午Kruskal重构树,其基本思路为“化边为点,以树代图”,将图重构为一棵树,将最值 、连通性等问题转化为LCA解决。

此处为模板代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+7;
int m,n,tot,fa[4*N],val[4*N],fat[4*N][60],deep[4*N];
vector<int> roots;
struct tree{
int w,v,u;
}e[4*N];
vector<int>G[4*N];
inline bool cmp(tree a,tree b) {
return a.w<b.w;
}
inline int findn(int x) {
return fa[x]==x?x:fa[x]=findn(fa[x]);
}
void dfs(int x,int father) {
deep[x]=deep[father]+1;
fat[x][0]=father;
for(int i=1;(1<<i)<=deep[x];i++) {
fat[x][i]=fat[fat[x][i-1]][i-1];
}
for(int i=0;i<G[x].size();i++) {
if(G[x][i]!=father)
dfs(G[x][i],x);
}
}
int lca(int x,int y) {
if(deep[x]<deep[y]) swap(x,y);
for(int i=19;i>=0;i--) {
if(deep[x]-(1<<i)>=deep[y])
x=fat[x][i];
}
if(x==y) return x;
for(int i=19;i>=0;i--) {
if(fat[x][i]!=fat[y][i]) {
x=fat[x][i];
y=fat[y][i];
}
}
return fat[x][0];
}
void kruskal() {
sort(e+1,e+m+1,cmp);
for(int i=1;i<=2*n;i++) fa[i]=i;
tot=n;
for(int i=1;i<=m;i++) {
int fu=findn(e[i].u);
int fv=findn(e[i].v);
if(fu!=fv) {
int nw=++tot;
val[nw]=e[i].w;
fa[fu]=fa[fv]=nw;
G[nw].push_back(fu);
G[nw].push_back(fv);
}
}
for(int i=1;i<=tot;i++) {
if(fa[i]==i) {
roots.push_back(i);
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>e[i].u>>e[i].v>>e[i].w;
kruskal();
for(int root : roots) {
dfs(root, 0);
}
int q;
cin>>q;
for(int i=1;i<=q;i++) {
int a,b;
cin>>a>>b;
if(findn(a) != findn(b)) {
cout<<"impossible\n";
} else {
int l = lca(a,b);
cout<<val[l]<<"\n";
}
}
return 0;
}
此处掌握不太牢固,择日专列一篇blog整理。
Day 5:
整体二分
Day 6:
主席树

浙公网安备 33010602011771号