CSP-S模拟20归隐 按位或 最短路径 最短路

[等比数列求和/矩阵加速递推]T1:定义\(a,b,c->a,a*b,b,b*c,c\)是一次操作,初始\(A={1,3}\),求n次操作后\(sigma(1<=i<=n)(log3(a1*a2*....*ax))\)是多少.(n<=1e18)

考场

发现第i次合并会让新序列的3次幂+\(3^i\),整理答案发现递推式子\(fi=(fi-1)*3-(i-2)\),但是复杂度不对,可是不会想了,暗示自己到这就差不多了(堕落!),于是50分TLE

正解

Sol1

按照递推思路,矩阵加速,注意顺序。\(O(3^3*log(n))\)

点击查看代码


//天将降大任于斯人,必先苦其心志:愈挫愈勇,愈挫愈智,愈挫愈静,愈挫愈赢!
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define ll long long 
#define ull unsigned long long
#define chu printf
#define rint register int
inline ll re()
{
    ll x=0,h=1;char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=5e5+100,M=5e6+100;
const int mod=998244353;
ll n;
#define int ll
struct calc
{
    int a[4][4];
    calc()
    {
        memset(a,0,sizeof(a));
    }
    friend calc operator * (calc x,calc y)
    {
        calc res;
        _f(i,1,3)
        _f(j,1,3)
        _f(k,1,3)
        res.a[i][j]=(1ll*res.a[i][j]+1ll*x.a[i][k]*y.a[k][j]%mod+mod)%mod;
        return res;
    }
}g,base;
inline calc qpow(calc ar,int br)
{
    calc cr;
    _f(i,1,3)cr.a[i][i]=1;
    while(br)
    {
        if(br&1)
        {
            cr=cr*ar;
        }
        ar=ar*ar;
        br>>=1;
    }
    return cr;
}
signed main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    freopen("gy.in","r",stdin);
   freopen("gy.out","w",stdout);
    n=re();
    if(n==1)
    {
        chu("1");return 0;
    }
    if(n==2)
    {
        chu("3");return 0;
    }
    g.a[1][1]=3;
    g.a[2][1]=1;
    g.a[3][1]=1;
    base.a[1][1]=3,base.a[1][2]=-1,base.a[1][3]=0;
    base.a[2][1]=0,base.a[2][2]=1,base.a[2][3]=1;
    base.a[3][1]=0,base.a[3][2]=0,base.a[3][3]=1;
    g=qpow(base,n-2)*g;
    chu("%lld",g.a[1][1]);
    return 0;
}
/*
*/

Sol2

从发现了ai的规律开始,\(sigma(0<=i<=n)(3^i)-->(3^{i+1}-1)/2\),递推式子就O(1)求出了,考虑第二维度就是等比数列,
\(S1=3^i+3^{i+1}+....+3^n\)
\(S2=3*(S1)\)
\(2*S1=S2-S1=3^{n+1}-3^i\)
很简单。然后\(O(log3(n))\)

[容斥原理计数]T2:求一个有序集合A,长度是n,满足ai是3的倍数,ai的或和是T。(n,T<=1e18)

考场

发现前i个数对第i+1个影响只在于|和,
\(f[i][j]表示前i个数|和是j的方案数\)
\(f[i][j]=sigma(f[i-1][k])(k|a[i]==j,3|a[i])\)
ai和j集合可以预处理,其实很少,1000个数只有200多个。
但是ai和j的集合不等效!不能开一个数组否则你就像我一样爆0了

正解

ai是3的倍数<=>(\(3|[2进制ai奇数位是1的个数]-[2进制ai偶数位是1的个数]\))
因为偶数位%3余1,奇数位%3余2,原始余数\(cnt_0*1+cnt_1*2是3的倍数\),这样判断也行。
然后就是容斥原理
\(f[i][j]表示最多i个奇数位是1,最多j个偶数位是1的数有多少种,那么f[t_0][t_1]^n就是答案\)
\(但是要减去不合法的,所以强制选C(t_1,i),C(t_0,j)可能1,其他都是0,这是冗余状态,容斥一下就行。\)

点击查看代码


//天将降大任于斯人,必先苦其心志:愈挫愈勇,愈挫愈智,愈挫愈静,愈挫愈赢!
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define ll long long 
#define ull unsigned long long
#define chu printf
#define rint register int
inline ll re()
{
    ll x=0,h=1;char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=70;
const int mod=998244353;
int cn1,cn0;
int c[N][N],f[N][N];
ll n,t;
inline void upd(int &a,int b)
{
    a=a+b;
    a=(a<0)?(a+mod):((a>=mod)?(a-mod):a);
}
inline int qpow(int a,int b)
{
    int c=1;int rem=b,rm=a;
    while(b)
    {
        if(b&1)c=(ll)c*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
  //  chu("%d %d:%d\n",rm,rem,c);
    return c;
}
int main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
   freopen("or.in","r",stdin);
   freopen("or.out","w",stdout);
    n=re(),t=re();
    f_(i,62,0)if((1ll<<i)&t)
    {
        if(i&1)cn1++;
        else cn0++;
    }
    _f(i,0,65)
    {
        c[i][0]=1;
        _f(j,1,i)
        {
            upd(c[i][j],(c[i-1][j-1]+c[i-1][j])%mod);
            //chu("c[%d][%d]:%d\n",i,j,c[i][j]);
        }
    }
    _f(i,0,cn1)
    _f(j,0,cn0)
    _f(p,0,i)//2
    _f(q,0,j)//1
    if((2*p+q)%3==0)
    {
        upd(f[i][j],(ll)c[i][p]*c[j][q]%mod);
    //    chu("f(%d,%d):%d\n",i,j,f[i][j]);
    }
    n%=(mod-1);
   // chu("n:%d\n",n);
    int ans=0;
    f_(i,cn1,0)
    f_(j,cn0,0)
    {
        int opt=(cn1+cn0-i-j)&1;
        if(opt)opt=-1;
        else opt=1;
        upd(ans,(ll)(mod+opt)*c[cn1][i]%mod*c[cn0][j]%mod*qpow(f[i][j],n)%mod);
        //chu("now ans:%d  %d*%d*%d*%d\n",ans,opt,c[cn1][i],c[cn0][j],qpow(f[i][j],n));
    }
    chu("%d",ans%mod);
    return 0;
}
/*
*/

[容斥原理+树上计数]T3:给出一棵树T,边权1,n个点,给出m个关键点,求选出K个关键点遍历所有K个路径和最小的路径和期望。(n<=2000,m<=300)

考场

对于K=2,路径唯一
对于K=m,考虑暴力算
补集思想
可以先算出所有点的路径和然后减去直径就是贡献
\(O(n^2)\)

正解

可以用总的点路径期望减去所有直径期望就是最小路径期望,
总路径贡献枚举边计算,直径贡献
【1】枚举点或者边作为直径交集,\(sigma(d)(C(cnt_{d}+cnt_{lessd},k)-C(cnt_{d},k)-(sigma (C(cnt_{son_d}+cnt_{son_lessd},k)-C(cnt_{son_d},k)))\),用所有选择深度<=d的减去只有<d的减去合法情况只在一棵子树里的就是所有直径是2d的直径作为中心点的贡献(直径长度偶数)方案
【2】枚举每个点x作为直径的一个端点,枚举另一个端点p,那么假设q满足\(dis(x,p)<dis(x,q)\),贡献就是\(C(cnt_q,k-2)*dis(x,p)\),但是注意p和q作为端点可能合法的情况,所有为了避免这种情况出现,把{x}点集合按照和任意点距离从大到小排序,那么任意x右侧的点不会参与到左侧形成直径的过程,就合法了

点击查看代码




//天将降大任于斯人,必先苦其心志:愈挫愈勇,愈挫愈智,愈挫愈静,愈挫愈赢!
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define ll long long 
#define ull unsigned long long
#define chu printf
#define rint register int
inline ll re()
{
    ll x=0,h=1;char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=2010;
const int mod=998244353;
int head[N],tot,n,K,m,siz[N],dis[N][N],dist[N],key[N],ans;
int sinv[N],inv[N],fac[N];
bool iskey[N];
struct node
{
    int to,nxt;
}e[N<<1];
inline int qpow(int a,int b)
{
    int c=1;
    while(b)
    {
        if(b&1)c=(ll)c*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
    return c;
}
inline void upd(int&a,int b)
{
    a=a+b;
    a=(a<0)?(a+mod):((a>=mod)?(a-mod):a);
}
inline void pre_work()
{
    sinv[0]=sinv[1]=fac[0]=fac[1]=inv[0]=inv[1]=1;
    _f(i,2,m)
    {
        fac[i]=(ll)fac[i-1]*i%mod;
        inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
        sinv[i]=(ll)sinv[i-1]*inv[i]%mod;
    }
}
inline void add_edge(int u,int v)
{
    e[++tot].to=v;
    e[tot].nxt=head[u];
    head[u]=tot;
}
inline void get_dis(int start,int x,int ff)
{
    for(rint i=head[x];i;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==ff)continue;
        dis[start][to]=dis[start][x]+1;
        get_dis(start,to,x);
    }
}
inline int C(int nr,int mr)
{
    if(nr<mr||mr<0||nr<0)return 0;
    return (ll)fac[nr]*sinv[mr]%mod*sinv[nr-mr]%mod;
}
inline void get_tot(int x,int ff)
{
    siz[x]=iskey[x];
    for(rint i=head[x];i;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==ff)continue;
        get_tot(to,x);
        siz[x]+=siz[to];
        int o=min(siz[to],K-1);
        _f(j,1,o)//最多选择多少关键点
        {
            upd(ans,(ll)2*C(siz[to],j)*C(m-siz[to],K-j)%mod);
        }
    }
}
inline void get_result()
{
    _f(i,1,m)
    {
        int p=0;int x=key[i];
        _f(j,i+1,m)
        {
            int y=key[j];
            dist[++p]=dis[x][y];
            //chu("%d ",dist[p]);
        }
        sort(dist+1,dist+1+p);
        f_(j,p,1)upd(ans,(ll)mod-(ll)C(j-1,K-2)*dist[j]%mod);
        
    }
}
inline bool cmp(int ar,int br)
{
    return dis[2][ar]>dis[2][br];
}
int main()
{
   // freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    n=re(),m=re(),K=re();
    pre_work();
    _f(i,1,m)iskey[key[i]=re()]=1;
    _f(i,1,n-1)
    {
        int u=re(),v=re();
        add_edge(u,v);add_edge(v,u);
    }
    _f(i,1,m)
    get_dis(key[i],key[i],0);
    get_dis(2,2,0);
    get_tot(1,0);
    //chu("%d\n",ans);
    sort(key+1,key+1+m,cmp);
    get_result();
    ans=(ll)ans*qpow(C(m,K),mod-2)%mod;
    chu("%d",ans);
    return 0;
}
/*
2 2  2
1 2
1 2

*/

【线段树加速高精度:DIJstra最短路】给出一个无向稀疏图,(x,y,z)表示有一条x-y的边,边权是\(2^z\),求从F到T的最短路。(n<=1e5,m<=2e5,z<=2e5)

考场

高精度运算,但是SPFA依旧会T!,用dij,只要重载运算符,就可以用高精度堆!
考虑对于z互不重复,容易想到路径长度的比较只和最高位1有关,但是不能跑最短路,因为假设\(egde(a,b,3),edge(c,b,4),假设用第一个边更新dis=dis(a,b)_{max},那么另一条边就不能更新了,但是答案有可能更优秀,spf不行,dij也不行\)
所以跑最小生成树,把二进制位直接看成数进行大小比较

正解

线段树维护进位操作,主席树每次修改·单独开一条链,不能在原来基础上修改,不然比较元素因为同一个点有多个会WA。
用hash直接判断某区间是否是相同。
竟然也可以重载运算符!

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define rint register int
#define chu printf
#define ll long long
#define ull unsigned ll
inline ll re()
{
	ll x=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')h=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*h;
}
const int N=2e5+100,M=2e5+100,maxn=2e5;
const ull mod=1e9+7;
const ull base=20060611;
int head[N],root[N],tot,fof,n,m,F,T,ans[M];//对点建立了点所代表的值的线段树
ull pw[M],fl[M];
bool wan[N];
struct node
{
    int to,nxt,w;
}e[M<<1];
struct dist
{
    int point,sig;//比较方式啊,是什么来着
};
struct segmentree
{
    int ls,rs;
    ull sum;
}t[M*50];//在位数的值域上开的线段树
inline void add_edge(int u,int v,int z)
{
    e[++tot]=(node){v,head[u],z};
    head[u]=tot;
}
//----------------------------------------------------------------------------
#define lson t[rt].ls
#define rson t[rt].rs
inline int new_seg(int rt)
{
    t[++fof]=t[rt];
    return fof;
}//都新开一个,没有其他的继承,正常应该是继承+修改
inline void pu_seg(int rt,int l,int mid,int r)
{
    t[rt].sum=(t[lson].sum*pw[r-mid]+t[rson].sum);
}
inline void update_seg(int&rt,int l,int r,int L,int R)
{
    rt=new_seg(rt);
    if(L<=l&&r<=R)//包含了
    {
        if(r==R&&l==r)t[rt].sum=1;
        else if(r==R)
        {
            int mid=(l+r)>>1;
            update_seg(lson,l,mid,L,R);
            update_seg(rson,mid+1,r,L,R);
            pu_seg(rt,l,mid,r);
        }
        else 
        {
            t[rt].sum=0;
            lson=rson=0;
        }
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update_seg(lson,l,mid,L,R);
    if(R>mid)update_seg(rson,mid+1,r,L,R);
    pu_seg(rt,l,mid,r);
}
inline int seperate_seg(int rt,int l,int r,int L,int R)//找到此区间线段最左边的1
{
    if(l==r)return (t[rt].sum==1)?1e9:l;
    if(L<=l&&r<=R)
    {
        int mid=(l+r)>>1;
        if(t[lson].sum==fl[mid-l+1])return seperate_seg(rson,mid+1,r,L,R);
        return seperate_seg(lson,l,mid,L,R);
    }
    int mid=(l+r)>>1;
    if(R<=mid)return seperate_seg(lson,l,mid,L,R);
    if(L>mid)return seperate_seg(rson,mid+1,r,L,R);
    return min(seperate_seg(lson,l,mid,L,R),seperate_seg(rson,mid+1,r,L,R));
}
inline bool compare_seg(int ra,int rb,int l,int r)
{
    if(!ra||!rb||!t[ra].sum||!t[rb].sum)return t[ra].sum>0;
    if(l==r)return t[ra].sum>t[rb].sum;
    int mid=(l+r)>>1;
    if(t[ra].sum==t[rb].sum)return 0;
    if(t[t[ra].rs].sum==t[t[rb].rs].sum)return compare_seg(t[ra].ls,t[rb].ls,l,mid);
    return compare_seg(t[ra].rs,t[rb].rs,mid+1,r);
}
inline void letitgo_seg(int rt,int l,int r)
{
    if(l==r)return ans[l]=t[rt].sum,void();
    int mid=(l+r)>>1;
    letitgo_seg(lson,l,mid);
    letitgo_seg(rson,mid+1,r);
}
//----------------------------------------------------------------------------
inline bool operator>(const dist A,const dist B)
{
    int wr=compare_seg(A.point,B.point,0,maxn);//A>B的定义方式
   // chu("wr:%d\n",wr);
    return wr;
}
priority_queue<dist,vector<dist>,greater<dist> >q;
inline void Dijstra(int st)
{
    q.push((dist){root[st],st});
 //   int tp=10000;
    while(!q.empty())
    {
      //  tp--;if(!tp){chu("op");break;}
        dist r=q.top();q.pop();
       // tp--;
      //  if(tp>0)chu("%d\n",r.sig);
        if(wan[r.sig]||root[r.sig]!=r.point)continue;
        wan[r.sig]=1;
      //  chu("fof:%d\n",fof);
        for(rint i=head[r.sig];i;i=e[i].nxt)
        {
            int to=e[i].to;
            if(wan[to])continue;
         //   if(tp==2)chu("00>to:%d\n",to);
            int pos=seperate_seg(root[r.sig],0,maxn,e[i].w,maxn);//找到从w位开始的第一个0在哪
            int now=root[r.sig];
            update_seg(now,0,maxn,e[i].w,pos);
            if(!root[to]||compare_seg(root[to],now,0,maxn))
            {
                root[to]=now;
                q.push((dist){root[to],to});
            }
        }
       // if(tp==98)chu("sdfd\n");
    }
}
inline void Calc()
{
    letitgo_seg(root[T],0,maxn);
    ull now=0,rem=1;
    for(rint i=0;i<=maxn;++i)
    {
      //  if(ans[i])chu("%d",ans[i]);
        now=(now+ans[i]*rem)%mod;
        rem=(rem*2%mod);
    }//chu("\n");
    chu("%llu",now);
}
int main()
{
    freopen("hellagur.in","r",stdin);
   freopen("hellagur.out","w",stdout);
    n=re(),m=re();
   // chu("jeje\n");
    for(rint i=1;i<=m;++i)
    {
        int x=re(),y=re(),z=re();
        add_edge(x,y,z);
        add_edge(y,x,z);
    }
    pw[0]=1,fl[0]=0;
    for(rint i=1;i<=maxn;++i)
    {
        pw[i]=pw[i-1]*base;
        fl[i]=fl[i-1]*base+1llu;//满1的值
    }
    F=re(),T=re();
    Dijstra(F);
    if(!root[T])chu("-1");
    else Calc();
	return 0;
}
/*
4 3
1 2 4
2 3 5
3 4 6
1 4
*/

低错【1】hash拼接长度搞错了【2】判断非法没考虑全【3】线段树seperate到覆盖区间竟然没判断【4】建图e[u].to=u!!!!f,我的天...

posted on 2022-10-20 21:52  HZOI-曹蓉  阅读(23)  评论(0编辑  收藏  举报