260824-260830 总结复盘

Title:260824-260830 总结复盘

Day 1:

省选模拟赛,T1为有限制的排列问题,好题,挣扎许久后AC
题目大意:从\(n\)个意象中选出\(m\)个(按顺序)组成诗,第\(i\)个意象必须是\(b[i]\)的倍数。
考虑:如果没有“第\(i\)个必须是\(b[i]\)的倍数”限制,则直接用排列数公式解决。
那么考虑容斥原理:全部的-2个重复的+3个重复的-…………以此类推。
可以得到容斥公式:

\[F[S]=∑ (−1)^{∣T∣−1} (∣T∣−1)!⋅I[T]⋅F[S∖T] \]

\((−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 = |P \cap S_i| \bmod 2 \]

若\(b_i = 1\),则称“愿望$i $被实现”。

所有被实现的愿望的下标按升序排列为$a_1 < a_2 < \dots < a_k $,我们定义比较方式为:
比较序列

\[[a_1, a_2, \dots, a_k, n+1] \]

的字典序,我们要使这个序列字典序最小。
然后需要:

  1. 对于每个\(i\),输出在最优情况下,愿望$ i $是否被实现(Yes/No)。
  2. 输出能达到这个最优字典序的不同$ P $ 的个数,对 $ 998244353 $ 取模。

显然,对于“字典序最小”的要求,好的贪心策略是从第一个开始,逐个查询愿望能否加入选择集合中,一旦不能就停止查询输出结果。
对于每一个愿望,由于其元素必须在集合\(U\)中出现奇数次,因此考虑对于每个愿望构造一个方程:

\[Σ(P_i × S_i) ≡ 1 (mod 2) \]

其中\(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\)
image
言归正传,T1为CSP-S 2024T1升级版,考虑用攻击力刚好大于该怪兽的第一个怪兽击杀当前怪兽,这样就不会浪费高攻击力的怪兽了。题很简单,就不放代码了。
T2数据范围可疑(\(n≤10^9\)),情况复杂多样,显然并非普通DP。该题有很大的探究价值,择日专列一篇blog复盘,此处不再展开。


下午Kruskal重构树,其基本思路为“化边为点,以树代图”,将图重构为一棵树,将最值 、连通性等问题转化为LCA解决。
image
此处为模板代码:

#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:

主席树

posted @ 2026-08-30 20:57  灿若星尘  阅读(19)  评论(0)    收藏  举报