FCS省选模拟赛 Day1

Description

 Solution



T1 shopping

目测是插板法乱搞一下

发现题解写的是容斥dp:
\[ ans = \sum_i (-1)^ig[i] \]
\(g[i]\)表示的有\(i\)个商店必然达到上限的方案数

考虑转化,设\(f[i][j]\)表示前\(i\)个商店,必然超过限制的商店的(上限+1)的和是\(j\)
\[ f[i][j]=f[i-1][j]-f[i-1][j-w[i]-1] \]
所以答案就可以这样计算:
\[ ans=\sum_i f[n][i] C(k+n-1,n-1) \]
后面那一块就是插板法

/*容斥dp */
#include<bits/stdc++.h>
#define ll long long
using namespace std;
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<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
const int MN=5e6+5,MM=301,mod=1e9+7;
int n,m,k,wi[MM],f[2][MM*MM+MN],fac[MN<<1],inv[MN<<1];
int C(int x,int y){if(y<=0)return 1;return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}
int fpow(int x,int M){int r=1;for(;M;M>>=1,x=1ll*x*x%mod)if(M&1)r=1ll*r*x%mod;return r;}
int main()
{
    register int i,j,qz=0,ans=0;
    n=read(),m=read(),k=read();
    for(i=1;i<=m;++i) wi[i]=read(),qz+=wi[i];
    for(fac[0]=i=1;i<=n+k;++i)fac[i]=1ll*fac[i-1]*i%mod;
    for(inv[n+k]=fpow(fac[n+k],mod-2),i=n+k-1;~i;--i)inv[i]=1ll*inv[i+1]*(i+1)%mod;
    f[1][wi[1]+1]=mod-1;f[1][0]=1;
    for(i=2;i<=m;++i)
    {
        for(j=0;j<=qz+m;++j)
        {
            int l=j-wi[i]-1;f[i&1][j]=f[(i&1)^1][j];
            if(l>=0) f[i&1][j]+=mod-f[(i&1)^1][l],f[i&1][j]%=mod;
        }
    }
    for(i=0;i<=k;++i)ans+=1ll*f[m&1][i]*C(k+n-i-1,n-1)%mod,ans%=mod;
    printf("%d\n",ans);
}



T2 highway

发现\(n\)很小,所以对于已经按照边权排号序的边集,求\(Kruskal\)生成森林的只要\(O(\alpha(n)n)\)

考虑线段树维护区间,每个节点保存用到的边(有序的),合并的时候做一次\(Kruskal\)即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
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<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
#define reg register
const int MN=105,MM=1e5+5;
int n,m,q;
struct edge{int u,v,w;}e[MM];
struct Node
{
    vector<int> ed;
    int val;
    Node(){ed.clear();val=0;}
}T[MM<<2];
int fa[MN];
int getf(int x){return x==fa[x]?x:fa[x]=getf(fa[x]);}
bool union_(int x,int y){x=getf(x);y=getf(y);if(x==y)return false;fa[x]=y;return true;}
Node merge(Node x,Node y)
{
    reg int si=x.ed.size(),sj=y.ed.size(),i,j;
    for(i=1;i<=n;++i) fa[i]=i;
    #define J y.ed[j]
    #define I x.ed[i]
    Node r;
    for(i=0,j=0;i<si||j<sj;)
    {
        if(i==si||(j!=sj&&e[J].w<e[I].w))
        {
            if(union_(e[J].u,e[J].v)) r.ed.push_back(J),r.val+=e[J].w;
            ++j;continue;
        }
        if(union_(e[I].u,e[I].v)) r.ed.push_back(I),r.val+=e[I].w;
        ++i;
    }
    return r;
}
void build(int x,int l,int r)
{
    if(l==r){T[x].ed.push_back(l);T[x].val=e[l].w;return;}
    int mid=(l+r)>>1;
    build(x<<1,l,mid);build(x<<1|1,mid+1,r);
    T[x]=merge(T[x<<1],T[x<<1|1]);
}
Node query(int x,int l,int r,int a,int b)
{
    if(l==a&&r==b) return T[x];
    int mid=(l+r)>>1;
    if(b<=mid) return query(x<<1,l,mid,a,b);
    if(a>mid) return query(x<<1|1,mid+1,r,a,b);
    else return merge(query(x<<1,l,mid,a,mid),query(x<<1|1,mid+1,r,mid+1,b));
}
int main()
{
    n=read();m=read();q=read();
    reg int i,l,r;
    for(i=1;i<=m;++i) e[i].u=read(),e[i].v=read(),e[i].w=read();
    build(1,1,m);
    while(q--)
    {
        l=read(),r=read();
        printf("%d\n",query(1,1,m,l,r).val);
    }
    return 0;
}



T3 sailing

第一步,我们考虑舰队可以”整体地“位于哪几个位置

把环境和舰队的图都转化成一个\(01\)序列,可以位于一个位置当且仅当在某个区间中没有两个数同为\(1\)

考虑把其中一个序列倒序处理,就可以用\(NTT\)算出上面的答案

第二步,可以位于某个位置并不代表一定可以移动的到

从一个一定满足的位置(比如说舰队的原位置)开始\(bfs\),显然所有合法的位置都是相互联通的

第三步,求可以到达的格子数

一个格子可以到达,说明将舰队的\(01\)序列放在某个合法的位置上,这个位置是\(1\)

显然也可以用\(NTT\)来解决

#include<bits/stdc++.h>
#define ll long long
using namespace std;
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<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
#define reg register
const int MN=705,P=998244353,g=3,invg=332748118;
int n,m;
int a[MN*MN*4],b[MN*MN*4],c[MN*MN*4],N,di,invN,pos[MN*MN*4];
char mp[MN][MN];
bool rea[MN][MN];
int fpow(int x,int m){int r=1;for(;m;m>>=1,x=1ll*x*x%P)if(m&1)r=1ll*r*x%P;return r;}
void NTT(int *a,int type)
{
    reg int wi,w,i,j,p,k;
    for(i=0;i<N;++i)if(i<pos[i])std::swap(a[i],a[pos[i]]);
    for(i=1;i<N;i<<=1)
    {
        wi=fpow(type>0?g:invg,(P-1)/(i<<1));
        for(j=i<<1,p=0;p<N;p+=j)
        for(w=1,k=0;k<i;++k,w=1ll*w*wi%P)
        {
            int X=a[p+k],Y=1ll*a[i+p+k]*w%P;
            a[p+k]=(X+Y)%P;a[i+p+k]=(X-Y+P)%P;
        }
    }
    if(type<0)for(i=0;i<N;++i)a[i]=1ll*a[i]*invN%P;
}
std::queue<std::pair<int,int> > q;
const int dx[4]={0,-1,0,1},dy[4]={1,0,-1,0};
void bfs(int x,int y)
{
    q.push(make_pair(x,y));rea[x][y]=false;
    while(!q.empty())
    {
        int X=q.front().first,Y=q.front().second;q.pop();
        a[(X-1)*m+Y]=1;
        for(reg int i=0;i<4;++i)if(rea[X+dx[i]][Y+dy[i]])
            rea[X+dx[i]][Y+dy[i]]=false,q.push(make_pair(X+dx[i],Y+dy[i]));
    }
}
int main()
{
    int i,j,x1=P,x2=-P,y1=P,y2=-P,ans=0;
    n=read();m=read();
    for(i=1;i<=n;++i) scanf("%s",mp[i]+1);
    for(i=1;i<=n;++i)for(j=1;j<=m;++j)
    {
        if(mp[i][j]=='#') a[(i-1)*m+j]=1;
        if(mp[i][j]=='o') x1=min(x1,i),x2=max(x2,i),y1=min(y1,j),y2=max(y2,j);
    }
    int T=(x2-x1)*m+y2-y1+1;
    for(i=1;i<=n;++i)for(j=1;j<=m;++j)
        if(mp[i][j]=='o') b[(i-x1)*m+j-y1+1]=c[T-(i-x1)*m-j+y1]=1;
    for(N=1,di=0;N<=m*n*2;N<<=1,++di);invN=fpow(N,P-2);
    for(i=0;i<N;++i) pos[i]=(pos[i>>1]>>1)|((i&1)<<(di-1));
    NTT(a,1);NTT(b,1);NTT(c,1);
    for(i=0;i<N;++i) a[i]=1ll*a[i]*c[i]%P;
    NTT(a,-1);
    for(i=1;i+(x2-x1)<=n;++i)for(j=1;j+(y2-y1)<=m;++j)if(a[T+(i-1)*m+j]==0)rea[i][j]=true;
    memset(a,0,sizeof a);bfs(x1,y1);NTT(a,1);
    for(i=0;i<N;++i) a[i]=1ll*a[i]*b[i]%P;
    NTT(a,-1);
    for(i=2;i<=n*m+1;++i) if(a[i]) ++ans;
    return 0*printf("%d\n",ans);
}




Blog来自PaperCloud,未经允许,请勿转载,TKS!

转载于:https://www.cnblogs.com/PaperCloud/p/FCS_noi2019_day1.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值