暑假集训题目合集-2

Day7

扫描线

一般使用线段树维护,具体地,维护区间和及区间非零个数。

[NOI2023] 方格染色

横竖线就是扫描线板题。斜线最多只有 \(5\) 个,暴力将能够合并的斜线合并,然后遍历所有横竖线判断是否有交。懒得写离散化了,直接动态开点线段树也能过。

code
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int N=1e5+10;
int tot,n,m,q,C,cnt,type3cnt,cnt1,cnt2;
ll ans;
bool vis[114];
struct line{
    int t,x1,y1,x2,y2;
}a[N],b[114],c[114];
struct change{
    int x,y1,y2,k;
    bool operator<(const change &temp)const{return x<temp.x;}
}p[N<<1];
struct Node{
    Node *ls,*rs;
    int val,tag;
}tree[N<<5];
Node *root;
inline Node *new_node(){return &tree[++tot];}
inline void push_up(Node *u,int l,int r){
    if(u->tag) u->val=r-l+1;
    else if(l==r) u->val=0;
    else{
        u->val=0;
        u->val+=u->ls?u->ls->val:0;
        u->val+=u->rs?u->rs->val:0;
    }
}
void modify(Node*&u,int l,int r,int x,int y,int k){
    if(u==nullptr) u=new_node();
    if(x<=l&&y>=r){
        u->tag+=k;
        push_up(u,l,r);
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) modify(u->ls,l,mid,x,y,k);
    if(y>mid) modify(u->rs,mid+1,r,x,y,k);
    push_up(u,l,r);
}
int main(){
    read(C,n,m,q);
    for(int i=1,t,x1,y1,x2,y2;i<=q;i++){
        read(t,x1,y1,x2,y2);
        if(t!=3){
            p[++cnt]={x1-1,y1,y2,1};
            p[++cnt]={x2,y1,y2,-1};
            a[++cnt1]={t,x1,y1,x2,y2};
        }
        else b[++type3cnt]={3,x1,y1,x2,y2};
    }
    sort(p+1,p+1+cnt);
    for(int i=1;i<=cnt;i++){
        modify(root,1,1e9,p[i].y1,p[i].y2,p[i].k);
        ans+=root->val*(p[i+1].x-p[i].x);
    }
    
    bool flag=1;
    while(flag){
        flag=0;
        for(int i=1;i<=type3cnt;i++){
            if(vis[i]) continue;
            for(int j=1;j<=type3cnt;j++){
                if(i==j||vis[j]) continue;
                if(b[j].x2-b[i].x1==b[j].y2-b[i].y1&&b[j].x1<=b[i].x2&&b[j].x2>=b[i].x2){
                    b[i].x1=min(b[i].x1,b[j].x1);
                    b[i].y1=min(b[i].y1,b[j].y1);
                    b[i].x2=max(b[i].x2,b[j].x2);
                    b[i].y2=max(b[i].y2,b[j].y2);
                    vis[j]=1;
                    flag=1;
                }
            }
        }
    }
    for(int i=1;i<=type3cnt;i++){
        if(vis[i]) continue;
        c[++cnt2]=b[i];
    }
    
    for(int i=1;i<=cnt2;i++){
        set<pair<int,int>> st;
        for(int j=1;j<=cnt1;j++){
            if(a[j].t==1){
                int tx=c[i].x1+a[j].y1-c[i].y1;
                if(tx<c[i].x1||tx>c[i].x2||tx<1||tx>n) continue;
                if(tx<a[j].x1||tx>a[j].x2) continue;
                pair<int,int> temp={tx,a[j].y1};
                if(st.find(temp)==st.end())
                    --ans,st.insert(temp);
            }
            else if(a[j].t==2){
                int ty=c[i].y1+a[j].x1-c[i].x1;
                if(ty<c[i].y1||ty>c[i].y2||ty<1||ty>m) continue;
                if(ty<a[j].y1||ty>a[j].y2) continue;
                pair<int,int> temp={a[j].x1,ty};
                if(st.find(temp)==st.end())
                    --ans,st.insert(temp);
            }
        }
        ans+=c[i].x2-c[i].x1+1;
    }
    printf("%lld\n",ans);
    return 0;
}

Day8

树上技巧

树的直径的求法:

  • 两遍 dfs/bfs(无法处理负边权)
  • 树形 DP(可以处理负边权)

这两种方法的时间复杂度均为 \(O(n)\)
树形 DP 具体求法:
\(dp_{u,0/1}\) 表示以 \(u\) 为根的子树中从根向下能延伸的最长/非严格次长路径。则答案为 \(dp_{u,0}+dp_{u,1}\) 的最大值。

[APIO2010] 巡逻

老题了。考虑贪心,\(k=1\) 时连接直径两端是显然的。设直径为 \((u_1,v_1)\),加入的第二条边为 \((u_2,v_2)\),二者不交时答案显然为 \(dis(u_1,v_1)+dis(u_2,v_2)\),若两路径有交,形如

则答案为 \(dis(u_1,v_2)+dis(u_2,v_1)\)。由此可以看出,若仍要选在直径上的边,代价会更大。则我们将直径的边权全部设为 \(-1\),再找一个新的直径。

code
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=1e5+10;
int n,k,ans,dis[N],a,b,pre[N],head[N],dp[N][2],maxn;
bool vis[N],tag[N];
vector<int> e[N];
void dfs1(int u,bool flag,int &x){
    vis[u]=1;
    for(int v:e[u]){
        if(vis[v]) continue;
        dis[v]=dis[u]+1;
        if(flag) pre[v]=u;
        if(dis[v]>dis[x]) x=v;
        dfs1(v,flag,x);
    }
}
void dfs2(int u,int fa){
    for(auto v:e[u]){
        if(v==fa) continue;
        dfs2(v,u);
        int w=tag[u]&&tag[v]?-1:1;
        int temp=dp[v][0]+w;
        if(temp>dp[u][0])
            dp[u][1]=dp[u][0],dp[u][0]=temp;
        else if(temp>dp[u][1])
            dp[u][1]=temp;
        maxn=max(maxn,dp[u][0]+dp[u][1]);
    }
}
int main(){
    read(n,k);
    for(int i=1,a,b;i<n;i++){
        read(a,b);
        e[a].push_back(b);
        e[b].push_back(a);
    }
    dfs1(1,0,a);
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    dfs1(a,1,b);
    if(k==1){
        ans=(n-1)*2-dis[b]+1;
        printf("%d\n",ans);
        return 0;
    }
    for(int i=b;i;i=pre[i]) tag[i]=1;
    dfs2(1,0);
    ans=n*2-dis[b]-maxn;
    printf("%d\n",ans);
    return 0;
}

[NOI2021] 轻重边

结论:每次修改操作染不同颜色,重边数等于区间内相邻两点颜色相同的点对数。树剖+线段树维护即可。精细处理树剖的查询部分。

code
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=1e5+10;
int t,n,m,siz[N],son[N],dep[N],fa[N],dfn[N],dfncnt,top[N];
vector<int> e[N];
void dfs1(int u,int f){
    fa[u]=f;
    siz[u]=1;
    son[u]=0;
    dep[u]=dep[f]+1;
    for(int v:e[u]){
        if(v==f) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]])
            son[u]=v;
    }
}
void dfs2(int u,int topf){
    dfn[u]=++dfncnt;
    top[u]=topf;
    if(son[u]) dfs2(son[u],topf);
    for(int v:e[u]){
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}
struct node{
    int lc,rc,cnt;
    inline friend node operator+(const node &a,const node &b){
        if(a.lc==-1) return b;
        if(b.lc==-1) return a;
        node res;
        res.lc=a.lc,res.rc=b.rc;
        res.cnt=a.cnt+b.cnt;
        if(a.rc==b.lc&&a.rc) ++res.cnt;
        return res;
    }
}tree[N<<2];
int tag[N<<2];
int P,DEP;
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
inline void push_down(int u,int siz){
    if(!tag[u]) return;
    tree[ls(u)]=tree[rs(u)]={tag[u],tag[u],(siz>>1)-1};
    tag[ls(u)]=tag[rs(u)]=tag[u];
    tag[u]=0;
}
inline void modify(int l,int r,int k){
    l+=P-1,r+=P+1;
    for(int i=DEP;i;i--) push_down(l>>i,1<<i),push_down(r>>i,1<<i);
    int siz=1;
    while(l^1^r){
        if(~l&1) tree[l^1]={k,k,siz-1},tag[l^1]=k;
        if(r&1) tree[r^1]={k,k,siz-1},tag[r^1]=k;
        l>>=1,r>>=1,siz<<=1;
        tree[l]=tree[ls(l)]+tree[rs(l)];
        tree[r]=tree[ls(r)]+tree[rs(r)];
    }
    for(l>>=1;l;l>>=1) tree[l]=tree[ls(l)]+tree[rs(l)];
}
inline node query(int l,int r){
    node resl={-1},resr={-1};
    l+=P-1,r+=P+1;
    for(int i=DEP;i;i--) push_down(l>>i,1<<i),push_down(r>>i,1<<i);
    while(l^1^r){
        if(~l&1) resl=resl+tree[l^1];
        if(r&1) resr=tree[r^1]+resr;
        l>>=1,r>>=1;
    }
    return resl+resr;
}
void change(int x,int y,int k){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])
            swap(x,y);
        modify(dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    modify(dfn[x],dfn[y],k);
}
int ask(int x,int y){
    int ans=0,c1=0,c2=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]){
            swap(x,y);
            swap(c1,c2);
        }
        node res=query(dfn[top[x]],dfn[x]);
        ans+=res.cnt;
        if(res.rc==c1&&c1) ++ans;
        c1=res.lc;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]){
        swap(x,y);
        swap(c1,c2);
    }
    node res=query(dfn[x],dfn[y]);
    ans+=res.cnt;
    if(res.rc==c2&&c2) ++ans;
    if(res.lc==c1&&c1) ++ans;
    return ans;
}
int cnt,op,a,b;
int main(){
    read(t);
    while(t--){
        dfncnt=0;
        for(int i=1;i<=n;i++) e[i].clear();
        read(n,m);
        for(int i=1,u,v;i<n;i++){
            read(u,v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        dfs1(1,0);
        dfs2(1,1);
        memset(tree,0,sizeof(node)*(n*4+10));
        memset(tag,0,sizeof(int)*(n*4+10));
        P=1,DEP=0;
        while(P<=n+1) P<<=1,++DEP;
        cnt=0;
        while(m--){
            read(op,a,b);
            if(op==1) change(a,b,++cnt);
            else printf("%d\n",ask(a,b));
        }
    }
    return 0;
}

[十二省联考 2019] 春节十二响

贪心策略:令两条链上各自权值最大的比较,次大的比较……则可以在每个节点开一个堆,启发式合并。不同于可并堆,节点 \(u\)\(v\) 的堆合并后的大小为 \(\max(size_u,size_v)\) 而非 \(size_u+size_v\),时间复杂度为 \(O(n\log n)\)

code
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int N=2e5+10;
int n,m[N];
ll ans;
vector<int> e[N];
priority_queue<int> q[N];
inline void merge(int x,int y){
    vector<int> temp;
    if(q[x].size()<q[y].size()) swap(q[x],q[y]);
    while(q[y].size()){
        temp.push_back(max(q[x].top(),q[y].top()));
        q[x].pop(),q[y].pop();
    }
    for(int k:temp) q[x].push(k);
}
void dfs(int u){
    for(int v:e[u]){
        dfs(v);
        merge(u,v);
    }
    q[u].push(m[u]);
}
int main(){
    read(n);
    for(int i=1;i<=n;i++) read(m[i]);
    for(int i=2,f;i<=n;i++)
        read(f),e[f].push_back(i);
    dfs(1);
    while(!q[1].empty()) ans+=q[1].top(),q[1].pop();
    printf("%lld\n",ans);
    return 0;
}

Day9

数论

「TAOI-2」喵了个喵 Ⅳ

神秘题。\(n\) 为偶数时显然。\(n\) 为奇数时先求出所有数的最大公约数 \(d\),此时取 \(x=2\),将所有数都约去 \(d\),此时若有奇数个奇数,则会有偶数个偶数,必然无解,反之则可以按照奇偶容易地构造一组解。最后答案为 \(2d\)

code
#include<cstdio>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=1e5+10;
int n,a[N],d,cntodd,cnteven;
bool ans[N];
int main(){
    read(n);
    if(n&1){
        for(int i=1;i<=n;i++){
            read(a[i]);
            d=__gcd(d,a[i]);
        }
        for(int i=1;i<=n;i++){
            a[i]/=d;
            if(a[i]&1) ++cntodd;
            else ++cnteven;
        }
        if(cntodd&1){
            printf("-1\n");
            return 0;
        }
        for(int i=1,cnt=0;i<=n&&cnt<cntodd/2-1;i++)
            if(a[i]&1) ans[i]=1,++cnt;
        for(int i=1,cnt=0;i<=n&&cnt<(cnteven+1)/2;i++)
            if(~a[i]&1) ans[i]=1,++cnt;
        printf("%d\n",2*d);
        for(int i=1;i<=n;i++)
            printf("%d",ans[i]);
    }
    else{
        printf("1\n");
        for(int i=1;i<=n/2;i++)
            printf("1");
        for(int i=1;i<=n/2;i++)
            printf("0");
    }
    return 0;
}

[CTSC2017] 吉夫特

这题的转化真是神了。发现题目中 \(\bmod 2\) 很特殊,\(\dbinom{a_{b_1}}{a_{b_2}} \times \dbinom{a_{b_2}}{a_{b_3}} \times \cdots \times \dbinom{a_{b_{k-1}}}{a_{b_k}} \bmod 2 >0\) 成立当且仅当对于任意 \(k>1\)\(\dbinom{a_{b_{k-1}}}{a_{b_k}}\bmod 2=1\),我们尝试使用 Lucas 定理展开

\[\binom{a_{b_{k-1}}}{a_{b_k}}\equiv \binom{\lfloor a_{b_{k-1}}/2\rfloor}{\lfloor a_{b_k}/2\rfloor}\binom{a_{b_{k-1}}\bmod 2}{a_{b_k}\bmod 2}\pmod 2 \]

持续展开,不难发现其等于两数二进制拆分后每一位的组合数相乘,又因为

\[\binom{1}{1}=\binom{1}{0}=\binom{0}{0}=1 \]

\[\binom{0}{1}=0 \]

所以 \(\dbinom{a_{b_{k-1}}}{a_{b_k}}\bmod 2=1\) 当且仅当 \(a_{b_k}\) 在二进制下是 \(a_{b_{k-1}}\) 的子集,即 \(a_{b_{k-1}} \operatorname{bitand} a_{b_k}=a_{b_k}\)。则我们可以推出 DP 方程,设 \(dp_{i}\) 表示以 \(i\) 为结尾的子序列方案数:

\[dp_{a_i}=\sum_{a_i\in a_j\land j<i}(dp_{a_j}+1) \]

\(O(3^{\log{\max(a_i)}})\) 子集枚举即可。

code
#include<cstdio>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int mod=1000000007,N=233350;
int n,a,dp[N],ans;
int main(){
    read(n);
    for(int i=1;i<=n;i++){
        read(a);
        for(int s=(a-1)&a;s;s=(s-1)&a)
            dp[s]=(dp[s]+dp[a]+1)%mod;
        ans=(ans+dp[a])%mod;
    }
    printf("%d\n",ans);
    return 0;
}

Day10

线性代数

[USACO07NOV] Cow Relays G

我们使用 \(\min\)\(+\) 代替原矩阵乘法中的 \(+\)\(\times\),也就是

\[C_{i,j}=\min(A_{i,k}+B_{k,j}) \]

\(A_{i,j}\) 表示 \(i\)\(j\) 的最短路,则走 \(n\) 步后的最短路即为 \(A_{i,j}^n\)
设离散化后点有 \(m\) 个,时间复杂度 \(O(m^3\log n)\)

code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=1010,M=210;
int n,t,s,e,a[N],cnt;
struct Edge{int u,v,w;}edge[M];
struct Matrix{
    int x[M][M];
    Matrix(){memset(x,0x3f,sizeof(x));}
    Matrix operator*(const Matrix &a)const{
        Matrix res;
        for(int k=1;k<=cnt;k++)
            for(int i=1;i<=cnt;i++)
                for(int j=1;j<=cnt;j++)
                    res.x[i][j]=min(res.x[i][j],x[i][k]+a.x[k][j]);
        return res;
    }
}b;
Matrix qpow(Matrix a,int b){
    Matrix res=a;
    --b;
    while(b){
        if(b&1) res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
int main(){
    read(n,t,s,e);
    for(int i=1,u,v,w;i<=t;i++){
        read(w,u,v);
        if(!a[u]) a[u]=++cnt;
        if(!a[v]) a[v]=++cnt;
        b.x[a[u]][a[v]]=b.x[a[v]][a[u]]=w;
    }
    b=qpow(b,n);
    printf("%d\n",b.x[a[s]][a[e]]);
    return 0;
}

【模板】矩阵求逆

本题中的矩阵均为方阵。
矩阵的逆:在模 \(p\) 意义下,若 \(B\times A=A\times B=I\),则 \(B\) 称为模 \(p\) 意义下 \(A\) 的逆矩阵,记作 \(A^{-1}\)。模 \(p\) 意义下,一个矩阵若有逆矩阵,则必然只有一个逆矩阵。
矩阵的逆可以用高斯-约旦消元求出。原理:使用初等行变换将矩阵 \(A\) 转化为单位矩阵 \(I\),同时对一个初始的单位矩阵 \(I\) 进行相同的初等行变换,最终得到的就是逆矩阵 \(A^{-1}\)
例如,我们构造原矩阵 \([A\mid I]\),进行一系列初等行变换后,得到 \([I\mid A^{-1}]\)。我们发现,高斯-约旦消元的过程就是将矩阵的左半部分变为单位矩阵的过程,非常适合求解矩阵的逆。
无解判断:若消元过程中,主元与 \(p\) 不互质则无解。

code
#include<cstdio>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int mod=1e9+7,N=410;
int n,a[N][N*2];
ll qpow(ll a,int b){
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int main(){
    read(n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            read(a[i][j]);
    for(int i=1;i<=n;i++)
        a[i][i+n]=1;
    for(int i=1,cur,line=1;i<=n;i++){
        cur=line;
        for(int j=line+1;j<=n;j++)
            if(a[j][i]>a[cur][i])
                cur=j;
        if(__gcd(a[cur][i],mod)!=1){
            printf("No Solution");
            return 0;
        }
        ll inv=qpow(a[cur][i],mod-2);
        for(int j=1;j<=(n<<1);j++)
            swap(a[cur][j],a[line][j]),a[line][j]=a[line][j]*inv%mod;
        for(int j=1;j<=n;j++)
            if(j!=line){
                ll temp=a[j][i];
                for(int k=1;k<=(n<<1);k++)
                    a[j][k]=((a[j][k]-a[line][k]*temp)%mod+mod)%mod;
            }
        ++line;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            printf("%d ",a[i][j+n]);
        printf("\n");
    }
    return 0;
}

Day11/12

离散与组合数学

[HAOI2008] 硬币购物

背包+容斥。首先求出没有任何限制下每种价值的方案数,设 \(dp_s\) 表示价值为 \(s\) 的方案数。子集枚举计算哪些硬币超出限制,若钦定第 \(i\) 中硬币超出限制,则该硬币至少总价值为 \(c_i\times (d_i+1)\),容斥计算即可。

code
#include<cstdio>
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int N=1e5+10;
int n,c[4],d[4],s;
ll dp[N];
int main(){
    read(c[0],c[1],c[2],c[3],n);
    dp[0]=1;
    for(int i=0;i<4;i++)
        for(int j=c[i];j<=1e5;j++)
            dp[j]+=dp[j-c[i]];
    while(n--){
        read(d[0],d[1],d[2],d[3],s);
        ll ans=dp[s];
        for(int i=0;i<(1<<4);i++){
            ll sum=0;
            int cnt=0;
            for(int j=0;j<4;j++)
                if(i>>j&1) sum+=c[j]*(d[j]+1ll),++cnt;
            int k=cnt&1?-1:1;
            if(s-sum<0||sum==0) continue;
            ans+=k*dp[s-sum];
        }
        printf("%lld\n",ans);
    }
    return 0;
}

Devu and Flowers

在无限制时,通过隔板可以得到答案为 \(\dbinom{n+s-1}{n-1}\),若加上限制则考虑容斥。枚举子集,则状态 \(S\) 中所有子集元素均不合法的方案数为 \(\dbinom{s-\sum_{i\in S}f_i-|S|-1}{n-1}\)。这里不方便直接预处理阶乘算组合数,但是注意到 \(\dbinom{n}{m}\) 中,\(n\)\(m\) 相差不会太大,所以可以直接通分后暴力计算。

code
#include<cstdio>
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=25,mod=1e9+7;
typedef long long ll;
int n;
ll qpow(ll a,int b=mod-2){
    a%=mod;
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
ll s,f[N],inv[N],ans,fact[N];
ll C(ll n,int m){
    if(n<0||m<0||n<m) return 0;
    ll res=1;
    for(ll i=n-m+1;i<=n;i++)
        res=res*(i%mod)%mod;
    return res*inv[m]%mod;
}
int main(){
    read(n,s);
    fact[0]=1;
    for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
    inv[n]=qpow(fact[n]);
    for(int i=n-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
    for(int i=0;i<n;i++) read(f[i]);
    for(int i=0;i<(1<<n);i++){
        int cnt=0;
        ll sum=0;
        for(int j=0;j<n;j++)
            if(i>>j&1)
                ++cnt,sum+=f[j];
        int k=cnt&1?-1:1;
        ans=(ans+k*C(s-sum-cnt+n-1,n-1)%mod)%mod;
    }
    printf("%lld\n",(ans+mod)%mod);
    return 0;
}

[CSP-S2019 江西] 多叉堆

并查集+组合。考虑合并过程。根一定是 \(0\),设合并前的大小为 \(size_x\)\(size_y\),方案数为 \(cnt_x\)\(cnt_y\),由于子树之间互不干扰,所以

\[cnt_{sum}=cnt_x\cdot cnt_y\cdot \binom{size_x+size_y-1}{size_x} \]

code
#include<cstdio>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int mod=1e9+7,N=3e5+10;
ll qpow(ll a,int b=mod-2){
    ll res=1;
    a%=mod;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int n,q,fact[N],inv[N],siz[N],fa[N],cnt[N];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
ll C(int n,int m){return (ll)fact[n]*inv[m]%mod*inv[n-m]%mod;}
int op,x,y,ans;
int main(){
    read(n,q);
    fact[0]=1;
    for(int i=1;i<=n;i++) fact[i]=(ll)fact[i-1]*i%mod;
    inv[n]=qpow(fact[n]);
    for(int i=n-1;i>=0;i--) inv[i]=(ll)inv[i+1]*(i+1)%mod;
    for(int i=1;i<=n;i++){
        fa[i]=i;
        siz[i]=1;
        cnt[i]=1;
    }
    while(q--){
        read(op,x);
        x=find((x+ans)%n+1);
        if(op==1){
            read(y);
            y=find((y+ans)%n+1);
            fa[x]=y;
            siz[y]+=siz[x];
            cnt[y]=(ll)cnt[y]*cnt[x]%mod*C(siz[y]-1,siz[x])%mod;
        }
        else{
            ans=cnt[x];
            printf("%d\n",ans);
        }
    }
    return 0;
}

[CEOI 2016] kangaroo

连续段 DP,一种很强的东西。\(dp_{i,j}\) 表示考虑放前 \(i\) 个数,构成 \(j\) 个合法连续段的方案数。在加入一个数时,可以有以下几种情况:

  • 并入某一个块,\(dp_{i-1,j}\times j\) 种情况。
  • 自成一个新块,\(dp_{i-1,j-1}\times j\) 种情况。
  • 将原来的两个块连接,合并成一个块,\(dp_{i-1,j+1}\times j\) 种情况。
    特判 \(s\)\(t\) 的限制。
code
#include<cstdio>
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=2e3+10,mod=1e9+7;
typedef long long ll;
int n,s,t,dp[N][N];
int main(){
    read(n,s,t);dp[1][1]=1;
    for(int i=2;i<=n;i++)
        for(int j=1;j<=i;j++){
            if(i==s||i==t) dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
            else{
                dp[i][j]=(ll)(j-(i>s)-(i>t))*dp[i-1][j-1]%mod;
                dp[i][j]=(dp[i][j]+(ll)dp[i-1][j+1]*j)%mod;
            }
        }
    printf("%d\n",dp[n][1]);
    return 0;
}

[CSP-S2019] Emiya 家今天的饭

考虑使用总方案数减不合法方案数得到答案。总方案数为

\[\prod_{i=1}^n\left(1+ \sum_{j=1}^ma_{i,j}\right)-1\]

由于超过 \(\left\lfloor\dfrac{k}{2}\right\rfloor\) 的食材最多只可能有一个,所以考虑直接枚举是哪一个食材超限。\(dp_{i,j}\) 表示前 \(i\) 种烹饪方案选菜后,选择的菜品数与已选的总数之差为 \(j\),转移见代码

code
#include<cstdio>
#include<cstring>
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=110,M=2010,mod=998244353;
typedef long long ll;
int n,m,a[N][M],sum[N],dp[N][N<<1];
ll ans;
int main(){
    read(n,m);
    ans=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            read(a[i][j]);
            sum[i]=(sum[i]+a[i][j])%mod;
        }
        ans=ans*(sum[i]+1)%mod;
    }
    ans=(ans-1+mod)%mod;
    for(int i=1;i<=m;i++){
        memset(dp,0,sizeof(dp));
        dp[0][n]=1;
        for(int j=1;j<=n;j++)
            for(int k=n-j;k<=n+j;k++)
                dp[j][k]=(dp[j-1][k]+(ll)dp[j-1][k-1]*a[j][i]%mod+(ll)dp[j-1][k+1]*(sum[j]-a[j][i]+mod)%mod)%mod;
        for(int j=1;j<=n;j++)
            ans=(ans-dp[n][j+n]+mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2025-07-20 21:10  headless_piston  阅读(15)  评论(0)    收藏  举报