模拟赛20200221(zjx)【带删除线性基(线段树分治),不经过某条边的最短路(最短路树),嵌套for循环循环次数(拉格朗日插值)】

8 篇文章 0 订阅
5 篇文章 0 订阅
T1:
题意:

给出 m m m个值在 [ 0 , 2 32 ) [0,2^{32}) [0,232)内的数, k k k次操作添加一个数或删除一个数,或询问某个 x x x是否能被这些数异或得到。
m , k ≤ 1 0 5 m,k\le10^5 m,k105

题解:

线性基套个线段树分治即可。网上好像有基于线性基性质的神奇做法,不过复杂度似乎都是两个log。

Code:

#include<bits/stdc++.h>
#define maxn 100005
#define ui unsigned int
using namespace std;
int n,m,k;
ui d[32],a[maxn],all;
char s[35];
map<ui,int>pre;
vector<ui>q[maxn<<2];
inline ui turn(char *s){
	ui x=0;
	for(int i=0;s[i];i++) x=x<<1|(s[i]-'0');
	return x;
}
inline bool ins(ui *d,ui x){
	for(int i=n-1;i>=0;i--) if(x>>i&1){
		if(!d[i]) return d[i]=x,1;
		x^=d[i];
	}
	return 0;
}
void add(int i,int l,int r,int x,int y,ui v){
	if(x<=l&&r<=y) {q[i].push_back(v);return;}
	int mid=(l+r)>>1;
	if(x<=mid) add(i<<1,l,mid,x,y,v);
	if(y>mid) add(i<<1|1,mid+1,r,x,y,v);
}
void solve(int i,int l,int r,const ui *d){
	ui t[32]; memcpy(t,d,sizeof t);
	for(int j=q[i].size()-1;j>=0;j--) ins(t,q[i][j]);
	if(l==r) {puts(ins(t,all^a[l])?"NO":"YES");return;}
	int mid=(l+r)>>1;
	solve(i<<1,l,mid,t),solve(i<<1|1,mid+1,r,t);
}
int main()
{
	freopen("lamp.in","r",stdin);
	freopen("lamp.out","w",stdout);
	int op; ui x;
	scanf("%d%d%d",&n,&m,&k),all=(1ll<<n)-1,k++;
	scanf("%s",s),a[1]=turn(s);
	for(int i=1;i<=m;i++) scanf("%s",s),pre[turn(s)]=1;
	for(int i=2;i<=k;i++){
		scanf("%d%s",&op,s),x=turn(s);
		if(!op) a[i]=x;
		else {a[i]=a[i-1]; if(pre[x]) add(1,1,k,pre[x],i-1,x),pre[x]=0; else pre[x]=i;}
	}
	for(map<ui,int>::iterator it=pre.begin();it!=pre.end();it++)
		if(it->second) add(1,1,k,it->second,k,it->first);
	solve(1,1,k,d);
}

T2
题意:

n n n个点 m m m条边的有向图,求1走到n再走到1的最短路径长度,可以反转一条边,反转的代价是 D i D_i Di
n ≤ 200 , m ≤ 50000 n\le200,m\le50000 n200,m50000

题解:

考试的时候重边距离没有取 min ⁡ \min min爆零了。。

考虑1到n的最短路,(n到1同理),如果反转一条边 ( x , y ) (x,y) (x,y)带来的影响是有两个:原来的最短路可能断掉了;新产生了 1 → y → x → n 1\to y\to x\to n 1yxn的路径。
我们建出最短路树。
对于第一个影响,如果断掉的边不在 1 1 1 n n n的树边上(条件可以放宽为不在树边上,不影响复杂度),那么最短路不变。
对于第二个影响,如果直接用原来的 d i s [ 1 ] [ y ] + w ( x , y ) + d i s [ x ] [ n ] dis[1][y]+w(x,y)+dis[x][n] dis[1][y]+w(x,y)+dis[x][n]有可能不合法,即 d i s [ 1 ] [ y ] dis[1][y] dis[1][y]经过了 ( x , y ) (x,y) (x,y)这条边,相当于要求不经过 ( x , y ) (x,y) (x,y) 1 1 1 y y y的最短路,如果 ( x , y ) (x,y) (x,y)不是 1 → y 1\to y 1y的树边,那么就是合法的。
对于在 ( x , y ) (x,y) (x,y)在树边上的情况,由于只有 O ( n ) O(n) O(n)条,重新建图跑 O ( n 2 ) O(n^2) O(n2)的Dijkstra即可。
复杂度 O ( n 3 + n m ) O(n^3+nm) O(n3+nm)

Code:

#include<bits/stdc++.h>
#define maxn 205
#define maxm 50005
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m,ans,dis[maxn][maxn],X[maxm],Y[maxm],C[maxm],D[maxm];
int fir[maxn],nxt[maxm],to[maxm],cst[maxm],tot;
inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,cst[tot]=z;}
struct Graph{
	int s,t,pre[maxn],w[maxn][maxn],d[maxn];
	bool vis[maxn],mark[maxm];
	int Dijkstra(int K){
		memset(w,0x3f,sizeof w),memset(vis,0,sizeof vis),memset(d,0x3f,sizeof d);
		for(int i=1;i<=m;i++) 
			if(i!=K) w[X[i]][Y[i]]=min(w[X[i]][Y[i]],C[i]);
			else w[Y[i]][X[i]]=min(w[Y[i]][X[i]],C[i]);
		d[s]=0;
		for(int o=1;o<n;o++){
			int k=0;
			for(int i=1;i<=n;i++) if(!vis[i]&&d[i]<d[k]) k=i;
			vis[k]=1;
			for(int i=1;i<=n;i++) if(!vis[i]) d[i]=min(d[i],d[k]+w[k][i]);
		}
		return d[t];
	}
	void dfs(int u){
		for(int i=fir[u],v;i;i=nxt[i]) 
			if(dis[s][u]+cst[i]==dis[s][v=to[i]]&&!pre[v])
				pre[v]=u,mark[i]=1,dfs(v);
	}
	int solve(int k){
		if(!mark[k]) return min(dis[s][Y[k]]+C[k]+dis[X[k]][t],dis[s][t]);
		else return Dijkstra(k);
	}
}G1,Gn;
int main()
{
	freopen("lane.in","r",stdin);
	freopen("lane.out","w",stdout);
	scanf("%d%d",&n,&m);
	memset(dis,0x3f,sizeof dis);
	for(int i=1;i<=n;i++) dis[i][i]=0;
	for(int i=1;i<=m;i++) scanf("%d%d%d%d",&X[i],&Y[i],&C[i],&D[i]),dis[X[i]][Y[i]]=min(dis[X[i]][Y[i]],C[i]),line(X[i],Y[i],C[i]);
	for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	ans=dis[1][n]+dis[n][1];
	G1.s=Gn.t=1,G1.t=Gn.s=n;
	G1.pre[1]=1,G1.dfs(1),Gn.pre[n]=n,Gn.dfs(n);
	for(int i=1;i<=m;i++){
		int x=G1.solve(i),y=Gn.solve(i);
		ans=min(ans,min(x+y,inf)+D[i]);
	}
	printf("%d\n",ans<inf?ans:-1);
}
T3:
题意:

n n n个嵌套起来的for循环,循环上界为 m m m。对于第 i i i个循环有两个参数 x i ( < i ) x_i(<i) xi(<i) c i c_i ci,如果 c i ≠ 0 c_i\neq 0 ci=0,那么它写作 f o r ( a i = c i ; a i ≤ m ; a i + + ) for(a_i=c_i;a_i\le m;a_i++) for(ai=ci;aim;ai++),否则,它写作 f o r ( a i = a x i ; a i ≤ m ; a i + + ) for(a_i=a_{x_i};a_i\le m;a_i++) for(ai=axi;aim;ai++)
n ≤ 4000 , m ≤ 1 0 18 n\le4000,m\le10^{18} n4000m1018

题解:

稍加思考可以发现 f o r for for循环的依赖关系形成了森林,对于一个 c i ≠ 0 c_i\neq0 ci=0的循环就是一棵树的根,树与树之间是独立的可以相乘。
对于一棵树可以跑一个简单的树形DP,设 f [ i ] [ j ] f[i][j] f[i][j]表示 i i i号循环还剩 j j j次到达 m m m时子树中的循环次数的总和。那么 f [ u ] [ i ] = ∑ j = 1 i ∏ f [ v ] [ j ] f[u][i]=\sum_{j=1}^i\prod f[v][j] f[u][i]=j=1if[v][j]。复杂度 O ( n m ) O(nm) O(nm)
在这里插入图片描述
Code(写这道题的时候意识到拉格朗日插值求幂和在 n ≤ m o d n\le mod nmod时是可以做到 O ( k l o g k ) O(klogk) O(klogk)的,但是当 n n n过大时分子就不一定有逆元了):

#include<bits/stdc++.h>
#define maxn 4005
#define LL long long
using namespace std;
const int mod = 998244353;
int n,D,siz[maxn],inv[maxn],f[maxn][maxn];
int fir[maxn],nxt[maxn],to[maxn],tot;
LL m,c[maxn];
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
void dfs(int u){siz[u]=1;for(int i=fir[u];i;i=nxt[i]) dfs(to[i]),siz[u]+=siz[to[i]];}
void solve(int u){
	fill(f[u]+1,f[u]+D+1,1);
	for(int i=fir[u],v;i;i=nxt[i]){
		solve(v=to[i]);
		for(int j=1;j<=D;j++) f[u][j]=1ll*f[u][j]*f[v][j]%mod;
	}
	for(int i=2;i<=D;i++) f[u][i]=(f[u][i]+f[u][i-1])%mod;
}
int main()
{
	freopen("tower.in","r",stdin);
	freopen("tower.out","w",stdout);
	scanf("%d%lld",&n,&m);
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1,x;i<=n;i++) scanf("%d%lld",&x,&c[i]),!c[i]&&(line(x,i),0);
	int ans=1;
	for(int i=1;i<=n;i++) if(!siz[i]){
		dfs(i),D=siz[i]+1,solve(i);
		int X=(m-c[i]+1)%mod,ret=0;
		for(int j=1;j<=D;j++){
			int Y=f[i][j];
			for(int k=1;k<=D;k++) if(j!=k) Y=1ll*Y*(X-k)%mod*(j>k?inv[j-k]:-inv[k-j])%mod;
			ret=(ret+Y)%mod;
		}
		ans=1ll*ans*ret%mod;
	}
	printf("%d\n",(ans+mod)%mod);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值