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,我的天...