• 正睿停课集训4


    正睿停课集训4

    思维不够发散,导致降智题目不会做

    A

    给定一棵树,每次可以移动到距离小于等于(3)的点上,求一个哈密顿回路

    首先,一条链我们可以奇偶跳

    一棵树,我们奇偶跳,最大距离不会超过三,所以降智题,直接根据深度奇偶分类

    奇数递归跳,偶数回溯跳即可

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<algorithm>
    #include<cstring>
    #include<cctype>
    #include<vector>
    #include<ctime>
    #include<cmath>
    #include<set>
    #include<map>
    #include<assert.h>
    #define LL long long
    #define pii pair<int,int>
    #define mk make_pair
    #define fi first
    #define se second
    using namespace std;
    const int N = 5e5 + 3;
    struct edge{
    	int to;
    	int nxt;
    }e[N << 1];
    int head[N],deep[N],md[N],size[N];
    int L[N],R[N],s[N],cnt;
    int n,m,tot;
    int ans[N],fa[N],t;
    bool flag[N];
    inline void add(int x,int y){
    	e[++tot].to = y;
    	e[tot].nxt = head[x];
    	head[x] = tot;	
    }
    inline int read(){
    	int v = 0,c = 1;char ch = getchar();
    	while(!isdigit(ch)){
    		if(ch == '-') c = -1;
    		ch = getchar();
    	}
    	while(isdigit(ch)){
    		v = v * 10 + ch - 48;
    		ch = getchar();
    	}
    	return v * c;
    }
    inline void dfs(int x,int f,int dep){
    	if(dep & 1) printf("%d ",x);
    	for(int i = head[x];i;i = e[i].nxt){
    		int y = e[i].to;
    		if(y == f) continue;
    		dfs(y,x,dep + 1);
    	}	
    if(!(dep & 1)) printf("%d ",x);
    }
    int main(){
    puts("Yes");
    	n = read();
    	for(int i = 1;i < n;++i){
    		int x = read(),y = read();
    		add(x,y);
    		add(y,x);	
    	}
    	dfs(1,0,1);
    	return 0;
    }
    

    B

    给定一个长度为的串(S)(m)个串(T),删掉第(i)个位置的代价为(w_i) 最小化使得(S)不包含任何(T)的代价(删掉之后两边不会拼到一起)

    (|T| le |S| le 2 imes 10^5 ,m le 10)

    首先,我们可以用kmp求出所有(S)(T)的匹配位置,之后考虑对于一个位置(r),若存在(l_1,l_2(l_1le l_2))使得([l_1,r])([l_2,r])(S)(T)的匹配位置,那么很明显([l_1,r])这个限制是没有用的.

    所以我们对于每一个位置,都只需要一个最右边的限制(如果存在的话),我们设为(P_i)

    所以我们设(f_i)表示当前在(i)位置删除的最小代价,我们转移就有

    [f_{i} = min f_j + a_i ]

    这样转移有点问题,因为我们要保证满足上面区间区间的限制,我们发现,更新完(f_i)之后,(P_i)之前的状态就没有用了,因为如果从(P_i)之前更新过来会导致不满足([P_i,i])这个限制

    那么我们更新完(f_i)之后,就使得(f_j = infty,(jin[0,P_i)),这样就确保不会从不合法的地方转移

    之后我们发现我们这个DP在区间查询最小值,将前缀赋值为(infty),直接线段树优化即可

    之后发现,区间赋值其实是单点赋值,因为每个位置只会被赋值为一次,而区间最小值我们可以改为查询后缀最小值,因为对于任意(j >i)有$f_j = infty $

    所以直接树状数组也可以

    之后我们又发现,树状数组也不需要,可以直接上单调队列优化DP

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<algorithm>
    #include<cstring>
    #include<cctype>
    #include<vector>
    #include<ctime>
    #include<cmath>
    #include<set>
    #include<map>
    #define LL long long
    #define pii pair<int,int>
    #define mk make_pair
    #define fi first
    #define se second
    using namespace std;
    const int N = 3e5 + 3;
    const int INF = 2e9 + 7;
    char s[N];
    int a[N],f[N];
    char t[11][N];
    int nxt[11][N];
    int len[11];
    int n,m;
    vector <pii> B,G;
    int Pi[N];
    inline int read(){
    	int v = 0,c = 1;char ch = getchar();
    	while(!isdigit(ch)){
    		if(ch == '-') c = -1;
    		ch = getchar();
    	}
    	while(isdigit(ch)){
    		v = v * 10 + ch - 48;
    		ch = getchar();
    	}
    	return v * c;
    }
    inline void kmp(int x){
    	nxt[x][1] = 0;
    	int j = 0;
    	for(int i = 2;i <= len[x];++i){
    		while(j && t[x][j + 1] != t[x][i]) j = nxt[x][j];
    		if(t[x][j + 1] == t[x][i]) j++;
    		nxt[x][i] = j;	
    	}
    	j = 0;
    	for(int i = 1;i <= n;++i){
    		while(j && t[x][j + 1] != s[i]) j = nxt[x][j];
    		if(t[x][j + 1] == s[i]) j++;
    		if(j == len[x]){
    			G.push_back(mk(i - len[x] + 1,i));
    			j = nxt[x][j];
    		}
    	}
    }
    inline bool cmp(pii x,pii y){
    	return x.se < y.se || (x.se == y.se && x.fi > y.fi);	
    }
    struct BIT{
    	int c[N];
    	inline void pre(){
    		for(int i = 1;i <= n + 1;++i) c[i] = INF;
    	}	
    	inline void ins(int x,int v){
    		c[x] = min(c[x],v);
    		for(;x <= n + 1;x += x & (-x)) c[x] = min(c[x],v);
    	}
    	inline int query(int x){
    		int res = INF;	
    		for(;x;x -= x & (-x)) res = min(res,c[x]);
    		return res;
    	}
    }T;
    int main(){
    	n = read();	m = read();
    	scanf("%s",s + 1);
    	for(int i = 1;i <= n;++i) a[i] = read();
    	for(int i = 1;i <= m;++i){
    		scanf("%s",t[i] + 1);
    		len[i] = strlen(t[i] + 1);
    		kmp(i);
    	}
    	sort(G.begin(),G.end(),cmp);
    	for(int i = 0;i < G.size();++i){
    		if(B.empty()){
    			B.push_back(G[i]);
    			Pi[G[i].se] = G[i].fi;
    			continue;	
    		}
    		else{
    			if(G[i].fi <= B.back().fi && G[i].se >= B.back().se) continue;
    			else B.push_back(G[i]);
    			Pi[G[i].se] = G[i].fi;
    		} 
    	}
    //	for(int i = 1;i <= n;++i) printf("%d ",Pi[i]);puts("");	
    	T.pre();
    	f[0] = 0;T.ins(n + 1,0);
    	int now = 0;int la = 0;
    	for(int i = 1;i <= n;++i){
    		f[i] = T.query(n - la + 1) + a[i];
    //		printf("%d %d
    ",i,T.query(n - la + 1));
    		T.ins(n - i + 1,f[i]);
    		la = max(la,Pi[i]);
    		while(now < Pi[i]) f[now] = INF,now++;
    	}
    //	for(int i = 0;i <= n;++i) printf("%d ",f[i]);puts("");
    	int ans = INF;
    	for(int i = 0;i <= n;++i) ans = min(ans,f[i]);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    C

    给定一棵树,每个节点的度数不超(L),(q)次询问,每次询问形如((u,v,k)),表示有(k)个小球

    给每一个小球指定一条路径,使得小球两两路径的交集恰好为((u,v )),由于球是往返运动,所以((u,v))是无序数对,求方案数

    两个方案不同,当且仅当有一个小球的路径不同(球区分,路径不区分)

    首先,我们发现对于((u,v)),实质上让我们求(u)子树内的合法点和(v)子树内的合法点的个数

    之后由于要求交集恰好为$(u,v) (,所以在)u(的子树内选点必须满足两两之间LCA为)u(,换句话说,)u(子树内部每一棵子树内最多选择一个点,但是特殊地,)u(可以选择多次,这个最后直接用组合数解决即可,接下来想一下如何求)u$的子树中有多少点可以选,转移直接树形背包

    [f_{i,j} = f_{i,j } + f_{i - 1,j - 1} imes size_k (kin son_i) ]

    之后我们DP出(u,v)对应的(dp)数组后

    就可以算贡献,另外,如果一个点是lca,那么这个点不能从另外一个点的子树方向转移

    时间复杂度是(O(qL^2))的,遗憾不能通过本题

    接下来考虑优化,发现大部分背包的状态都是可以利用的

    由于背包具有交换律,所以这一我们引入一个新的做法:退背包

    所谓的退背包,就是已经知道所有物品的一个DP背包,现在限制一个物品不能用,上面说到背包就有交换律,所以我们可以强制假设我们要退的这个物品是最后一个加入背包的,根据上面的方程式,我们很容易写出下面的代码

    inline void ins(int x,int sz){
    	for(int i = d[x] - 1;i >= 0;--i)
    	dp[x][i + 1] = mo1(dp[x][i + 1] + 1ll * dp[x][i] * sz % mod);
    }
    inline void del(int x,int sz){
    	for(int i = 0;i < d[x];++i){
    		dp[x][i + 1] = mo2(dp[x][i + 1] - 1ll * dp[x][i] * sz % mod); 	
    	}
    }
    

    注意枚举循序的差异,因为树形背包本质就是使用了滚动数组

    这样对于一个((u,v))我们只需要将黄色部分和蓝色部分退掉即可

    最终答案我们就最后枚举(u)被选择的次数,一个子树内的答案就是

    [sum_{i = 0}^k inom{k}{i} imes dp_{u,k - i} imes (k -i)! ]

    最终将两个子树内的答案乘起来就OK

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<algorithm>
    #include<cstring>
    #include<cctype>
    #include<vector>
    #include<ctime>
    #include<cmath>
    #include<set>
    #include<map>
    #include<assert.h>
    #define LL long long
    #define pii pair<int,int>
    #define mk make_pair
    #define fi first
    #define se second
    using namespace std;
    const int N = 4e5 + 3;
    const LL mod = 998244353;
    struct edge{
    	int to;
    	int nxt;
    }e[N << 1];
    int head[N],size[N],deep[N];
    int fac[N],inv[N];
    int dp[N][505];
    int fa[21][N],d[N];
    int n,m,L,tot;
    inline int read(){
    	int v = 0,c = 1;char ch = getchar();
    	while(!isdigit(ch)){
    		if(ch == '-') c = -1;
    		ch = getchar();
    	}
    	while(isdigit(ch)){
    		v = v * 10 + ch - 48;
    		ch = getchar();
    	}
    	return v * c;
    }
    inline LL quick(LL x,LL y){
    	LL res = 1;
    	while(y){
    		if(y & 1) res = res * x % mod;
    		y >>= 1;
    		x = x * x % mod;
    	}
    	return res;
    }
    inline void add(int x,int y){
    	e[++tot].to = y;
    	e[tot].nxt = head[x];
    	head[x] = tot;
    	d[y]++;	
    }
    inline void dfs(int x,int f,int dep){
    	deep[x] = dep;
    	fa[0][x] = f;
    	size[x] = 1;
    	for(int i = head[x];i;i = e[i].nxt){
    		int y = e[i].to;
    		if(y == f) continue;
    		dfs(y,x,dep + 1); 
    		size[x] += size[y];
    	} 	
    }
    inline int mo1(int x){
    	if(x >= mod) x -= mod;
    	 return x;	
    }
    inline int mo2(int x){
    	if(x < 0) x += mod;
    	return x;	
    }
    inline void ins(int x,int sz){
    	for(int i = d[x] - 1;i >= 0;--i)
    	dp[x][i + 1] = mo1(dp[x][i + 1] + 1ll * dp[x][i] * sz % mod);
    }
    inline void del(int x,int sz){
    	for(int i = 0;i < d[x];++i){
    		dp[x][i + 1] = mo2(dp[x][i + 1] - 1ll * dp[x][i] * sz % mod); 	
    	}
    }
    inline int LCA(int x,int y){
    	if(deep[x] < deep[y]) swap(x,y);
    	for(int i = 19;i >= 0;--i)
    		if(deep[fa[i][x]] >= deep[y]) x = fa[i][x];
    	if(x == y) return x;
    	for(int i = 19;i >= 0;--i)
    		if(fa[i][x] != fa[i][y]) x = fa[i][x],y = fa[i][y];
    	return fa[0][x];	
    }
    inline int kfa(int x,int k){
    	for(int i = 19;i >= 0;--i) if(k & (1 << i)) x = fa[i][x];
    	return x;	
    }
    inline int C(int n,int m){
    	return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod; 	
    }
    bool flag = 0;
    int main(){
    //	freopen("C.in","r",stdin);
    //	freopen("C.out","w",stdout);
    	n = read(),m = read(),L = read();
    	for(int i = 1;i < n;++i){
    		int x = read(),y = read();
    		add(x,y);
    		add(y,x);	
    	}
    	fac[0] = inv[0] = 1;
    	for(int i = 1;i <= L;++i) fac[i] = 1ll * fac[i - 1] * i % mod;
    	inv[L] = quick(fac[L],mod - 2);
    	for(int i = L - 1;i >= 1;--i) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
    	dfs(1,0,1); 
    	//for(int i = 1;i <= n;++i) printf("%d ",size[i]);puts("");
    	for(int j = 1;j < 20;++j){
    		for(int i = 1;i <= n;++i)
    			fa[j][i] = fa[j - 1][fa[j - 1][i]];
    	}
    	for(int i = 1;i <= n;++i){
    		dp[i][0] = 1; 
    		for(int j = head[i];j;j = e[j].nxt){
    			int y = e[j].to;
    			int sz = (y == fa[0][i] ? n - size[i] : size[y]);
    			ins(i,sz);
    		}
    	}
    	while(m--){
    		int x = read(),y = read(),k = read();
    		int lca = LCA(x,y),sz1,sz2;
    	//	printf("%d
    ",lca); 
    		if(deep[x] > deep[y]) swap(x,y);
    		sz1 = (x == lca ? size[kfa(y,deep[y] - deep[x] - 1)] : n - size[x]);
    		sz2 = n - size[y];
    		del(x,sz1),del(y,sz2);
    		int ans1 = 0,ans2 = 0;
    		for(int i = 0;i <= k;++i){
    			ans1 = mo1(ans1 + 1ll * C(k,i) * dp[x][k - i] % mod * fac[k - i] % mod);
    			ans2 = mo1(ans2 + 1ll * C(k,i) * dp[y][k - i] % mod * fac[k - i] % mod);
    		}
    		printf("%lld
    ",1ll * ans1 * ans2 % mod);
    		ins(x,sz1),ins(y,sz2);
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    ORA-01439: column to be modified must be empty to change datatype
    解决rhel相关系统下yum找不到安装包的解决方法
    oracle的function和procedure返回值给shell
    [摘] SQLPLUS Syntax
    Unix/Linux中shell调用sqlplus的方式
    sqlplus连接数据库的4种方式
    shell调用sqlplus批量执行sql文件
    shell自定义函数
    Java创建Oracle数据库表
    TCP的三次握手(建立连接)和四次挥手(关闭连接)
  • 原文地址:https://www.cnblogs.com/wyxdrqc/p/11722280.html
Copyright © 2020-2023  润新知