2023.11.01 考试总结&题解

总结

T1 copy

标签:杂项 \(C\)

这道题我们可以看到 \(k\) 非常的小,所以说我们可以尝试 正难则反,对于每一个位置,我们将操作反着做一遍,可以得到该点再一开始的时候的位置,时间复杂度:\(O(nk)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8;

int k,m,n;
char s[NN];

struct Operation{
	ll l,r,x;
	ll operator * (ll pos){
		ll len = r - l + 1;
		if(pos <= x) return pos;
		else if(x+1 <= pos && pos <= x + len) return l + (pos - x - 1);
		else return pos - len;
	}
}p[NN];

ll solve(ll x){
	for(int i = n; i >= 1; --i){
		x = p[i] * x;
	}
	return x;
}

int main(){
	freopen("copy.in","r",stdin);
	freopen("copy.out","w",stdout);
	scanf("%d%d",&k,&m);
	scanf("%s",s+1);
	scanf("%d",&n);
	for(int i = 1; i <= n; ++i){
		scanf("%d%d%d",&p[i].l,&p[i].r,&p[i].x);
		++p[i].l;p[i].r;p[i].x;
	}
	for(int i = 1; i <= k; ++i){
		putchar(s[solve(i)]);
	}
	puts("");
}

T2 hard

标签:DP \(B\)

我们可以发现,我们对于每一种方案,都可以转化为最多只有两个操作覆盖再一个点上,并且赋值操作在翻转操作前面

我们记 \(f_{i,0/1/2/3}\) 表示前 \(i\) 个球,在该点 无操作/赋值/翻转/先赋值再翻转。

式子如下:

\[f_{i,0} = \begin{cases} \min\{f_{i-1,0/1/2/3}\} & a_i = b_i\\ INF & a_i \neq b_i \end{cases} \]

\[f_{i,1} = \begin{cases} INF & a_i = b_i\\ \min\{f_{i-1,0}+1,f_{i-1,1},f_{i-1,2}+1,f_{i-1,3}\} & a_i \neq b_i \end{cases} \]

\[f_{i,2} = \begin{cases} \min\{f_{i-1,0}+1,f_{i-1,1}+1,f_{i-1,2},f_{i-1,3}+1\} & b_{i-1} = b_i\\ \min\{f_{i-1,0}+1,f_{i-1,1}+1,f_{i-1,2},f_{i-1,3}+1\} & b_{i-1} \neq b_i \end{cases} \]

\[f_{i,2} = \begin{cases} \min\{f_{i-1,0}+2,f_{i-1,1}+2,f_{i-1,2}+2,f_{i-1,3}\} & b_{i-1} = b_i\\ \min\{f_{i-1,0}+2,f_{i-1,1}+2,f_{i-1,2}+1,f_{i-1,3}+2\} & b_{i-1} \neq b_i \end{cases} \]

#include<bits/stdc++.h>
using namespace std;
const int NN = 1e6 + 8,INF = 0x3f3f3f3f;
int n;
bool a[NN],b[NN];
int f[NN][4];

int main(){
	freopen("hard3.in","r",stdin);
	freopen("hard.out","w",stdout);
	scanf("%d",&n);
	for(int i = 1; i <= n; ++i) scanf("%1d",&a[i]);
	for(int i = 1; i <= n; ++i) scanf("%1d",&b[i]);
	
	f[0][1] = f[0][2] = f[0][3] = INF;
	for(int i = 1; i <= n; ++i){
		
		/*i上没有任何操作*/
		if(a[i] != b[i]) f[i][0] = INF;
		else f[i][0] = min(min(f[i-1][0], f[i-1][1]), min(f[i-1][2], f[i-1][3]));
		
		/*i上有翻转操作*/
		if(a[i] == b[i]) f[i][1] = INF;
		else f[i][1] = min(min(f[i-1][0]+1, f[i-1][1]), min(f[i-1][2]+1, f[i-1][3]));
		
		/*i上有赋值操作*/
		f[i][2] = min(f[i-1][0]+1, f[i-1][1]+1);
		if(b[i-1] == b[i]) f[i][2] = min(f[i][2], min(f[i-1][2], f[i-1][3]+1));
		else f[i][2] = min(f[i][2],min(f[i-1][2]+1, f[i-1][3]));
		
		/*i上有赋值翻转操作*/
		f[i][3] = min(f[i-1][0]+2,f[i-1][1]+2);
		if(b[i-1] != b[i]) f[i][3] = min(f[i][3],min(f[i-1][2]+1,f[i-1][3]+2));
		else f[i][3] = min(f[i][3],min(f[i-1][2]+2,f[i-1][3]));
	}
	printf("%d",min(min(f[n][0],f[n][1]),min(f[n][2],f[n][3])));
}

T3 talk

原题:P8162 [JOI 2022 Final] 让我们赢得选举 (Let's Win the Election)

标签:杂项 \(C\) | DP \(B^-\)

考虑显然我们不知道有多少个支持州,而此问题显然关于支持州的个数有凸性,显然可以先套一个 三分答案/二分斜率上去。

然后我们的的贪心显然是 \(b\) 从小到大选,但显然是错误的。

如果说不选当前的 \(b_i\) 的话,那么一定是因为 \(a_i\) 的代价更小,更该选,所以说不选 \(b\) 的话,一定选 \(a\)

于是我们就可以设 \(f_{i,j}\) 表示选了 \(b\) 从小到大的前 \(i\) 个州,其中选了 \(j\) 个支持州的最小花费时间。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
const int NN = 1e3 + 8;
int n,m;

struct State{
	ld a,b;
	bool operator < (const State &x)const{
		return b < x.b;
	}
}s[NN];

ld res[NN];
ld f[NN][NN];
ld sum[NN];

ld check(int k){
	if(res[k] != 1e18) return res[k];
	for(int i = 0; i <= n; ++i){
		for(int j = 0; j <= n; ++j) f[i][j] = 1e18;
	}
	f[0][0] = 0.0;
	for(int i = 1; i <= n; ++i)
		for(int j = 0; j <= k; ++j)
		{
			f[i][j] = min(f[i][j],f[i-1][j] + s[i].a / (k + 1));
			if(j && s[i].b != 1e18) f[i][j] = min(f[i][j],f[i-1][j-1] + s[i].b / j);	
		}
	for(int i = k; i <= m; ++i)
		res[k] = min(res[k],f[i][k] + sum[i] / (ld)(k + 1));
	return res[k];
}
bool cmp(State &x,State &y){return x.a < y.a;}

int main(){
	freopen("talk.in","r",stdin);
	freopen("talk.out","w",stdout);
	scanf("%d%d",&n,&m);
	int cntb = 0;
	res[0] = 1e18;
	for(int i = 1; i <= n; ++i){
		scanf("%Lf%Lf",&s[i].a,&s[i].b);
		cntb += s[i].b != -1; 
		if(s[i].b == -1) s[i].b = 1e18;
		res[i] = 1e18;
	}
	sort(s+1,s+1+n);
	for(int i = 0; i <= m; ++i){
		sort(s+i+1,s+1+n,cmp);
		for(int j = i+1; j <= m; ++j)
			sum[i] = sum[i] + s[j].a;
		sort(s+i+1,s+1+n);
	}
	cntb = min(cntb,m);
	int l = 0,r = cntb;
	ld ans = 1e18;
	while(l <= r){
		int lt = (l*2+r) / 3,rt = (l+r*2) / 3;
		ld lans = check(lt),rans = check(rt);
		ans = min(ans,min(lans,rans));
		if(rans > lans) r = rt-1;
		else l = lt+1;
	}
	printf("%.15Lf",ans);
	
} 

T4 tree

改编原题:P5439 【XR-2】永恒

标签:DS \(B\) | 图论 \(B^+\) | 数学 \(C\)

遇到这么多个 \(\sum\),当然选择拆贡献分别计算。记点 \(u\)\(T_1\)上的子树大小为 \(siz_u\),深度为 \(dep_u\)
考虑 \(f(u,v)\) 被计入答案的次数:
\(u\)\(v\) 没有祖先关系,答案显然为 \(siz_u\times siz_v \times f(u,v)\)

否则, 不妨令 \(u\)\(v\) 的祖先,则答案为 \((n-siz_{jump(v,u)})\times siz_v \times f(u,v)\)

\(jump(v,u)\) 表示 \(v\) 所在的 \(u\) 的儿子的子树

对于第一类贡献,考虑在 \(T_2\) 上插入所有点,并查询。
对于第二类贡献,考虑在 dfs \(T_1\) 过程中逐个插入,并查询。
接下来,只需要关注如何插入和查询点 。对于插入,在 \(T_2\) 上把根节点到 \(u\) 的路径上的点加权值。
对于查询,在 \(T_2\) 上对根节点到 \(u\) 的路径上的点权求和即可。
可以证明上述操作均正确。使用树剖进行链加链求和,复杂度 \(O(n\log^2 n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int NN = 1e6 + 8,MM = 1e6 + 8;

int n,m;
int a[NN];
ll ans;
ll inv2 = (MOD+1) / 2;

struct Seg{
	int l,r;
	ll num,tag;
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define num(x) tree[x].num
	#define tag(x) tree[x].tag
	#define ls(x) (x << 1)
	#define rs(x) (x << 1 | 1)
}tree[MM << 2];
void addlz(int x,ll num){
	num(x) += (r(x)-l(x)+1) * num % MOD;
	tag(x) += num;
	if(num(x) >= MOD) num(x) -= MOD;
	if(tag(x) >= MOD) tag(x) -= MOD;
}
void pushdown(int x){
	addlz(ls(x),tag(x));
	addlz(rs(x),tag(x));
	tag(x) = 0;
}
void pushup(int x){
	num(x) = num(ls(x)) + num(rs(x));
}
void build(int x,int l,int r){
	l(x) = l; r(x) = r;
	if(l == r) return;
	int mid = (l + r) / 2;
	build(ls(x),l,mid);
	build(rs(x),mid+1,r);
}
void modify(int x,int l,int r,ll num){
	if(l <= l(x) && r(x) <= r){
		addlz(x,num);
		return;
	}
	int mid = (l(x) + r(x)) / 2;
	pushdown(x);
	if(l <= mid) modify(ls(x),l,r,num);
	if(mid + 1 <= r) modify(rs(x),l,r,num);
	pushup(x);
}
ll query(int x,int l,int r){
	if(l <= l(x) && r(x) <= r) return num(x);
	ll mid = (l(x) + r(x)) / 2,ans = 0;
	pushdown(x);
	if(l <= mid) ans += query(ls(x),l,r);
	if(mid + 1 <= r) ans += query(rs(x),l,r);
	return ans % MOD;
}



struct Tree{
	struct Edge{
		int to,next;
	}edge[MM];
	int head[MM],cnt,root;
	int siz[NN];
	void init(){
		memset(head,-1,sizeof(head));
		cnt = 1;
	}
	void add_edge(int u,int v){
		edge[++cnt] = {v,head[u]};
		head[u] = cnt;
	}
}T1,T2;


int fa[NN],dep[NN],son[NN],top[NN];
int dfn[NN],timet,R[NN];

ll query_link(int u){
	ll res = 0;
	while(top[u]){
		res = (res + query(1,dfn[top[u]],dfn[u])) % MOD;
		u = fa[top[u]];
	}
	return res;
}
void add_link(int u,int num){
	while(top[u]){
		modify(1,dfn[top[u]],dfn[u],num);
		u = fa[top[u]];
	}
	return;
}

void dfs1(int u){
	T1.siz[u] = 1;
	for(int i = T1.head[u]; i != -1; i = T1.edge[i].next){
		int v = T1.edge[i].to;
		dfs1(v);
		T1.siz[u] += T1.siz[v];
	}
}
void dfs2(int u){
	ans = (ans + 1ll * query_link(a[u]) * T1.siz[u] % MOD) % MOD;
	for(int i = T1.head[u]; i != -1; i = T1.edge[i].next){
		int v = T1.edge[i].to;
		add_link(a[u],n-T1.siz[u]-T1.siz[v]);
		dfs2(v);
		add_link(a[u],T1.siz[u]+T1.siz[v]-n);
	}
}
ll s[NN],t[NN];
void dfs3(int u,int fa){
	T2.siz[u] = 1;
	::fa[u] = fa;dep[u] = dep[fa]+1;
	
    for(int i = T2.head[u]; i != -1; i = T2.edge[i].next){
    	int v = T2.edge[i].to;
    	dfs3(v,u);
    	T2.siz[u] += T2.siz[v];
    	s[u] = (s[u] + s[v]) % MOD;//????????????
    	if(T2.siz[v] > T2.siz[son[u]]) son[u] = v;
	}
}
void dfs4(int u,int top){
	dfn[u] = ++timet; ::top[u] = top;
	if(son[u]) dfs4(son[u],top);
	for(int i = T2.head[u]; i != -1; i = T2.edge[i].next){
		int v = T2.edge[i].to;
		if(v == son[u]) continue;
		dfs4(v,v);
	}
	t[dfn[u]] = (t[dfn[u]] + s[u]) % MOD;
	t[timet+1] = (t[timet+1] + MOD - s[u]) % MOD;
}

int main(){
	T1.init();T2.init();
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d%d",&n,&m);
	build(1,1,m);
	for(int i = 1,x; i <= n; ++i){
		scanf("%d",&x);
		if(x == 0) T1.root = i;
		else T1.add_edge(x,i);
	}
	dfs1(T1.root);
	for(int i = 1,x; i <= m; ++i){
		scanf("%d",&x);
		if(x == 0) T2.root = i;
		else T2.add_edge(x,i);
	}
	for(int i = 1; i <= n; ++i){
		scanf("%d",&a[i]);
		s[a[i]] += T1.siz[i];
	}
	dfs3(T2.root,0);dfs4(T2.root,T2.root);
//    for(int i=1;i<=100;++i)cerr<<dep[i]<<" ";
    for(int i = 1;i <= m; ++i)
		t[i] = (t[i] + t[i-1]) % MOD;
    
    for(int i = 1; i <= n; ++i) 
		ans = (ans + (t[dfn[a[i]]] - 1ll * dep[a[i]] * T1.siz[i] % MOD) * T1.siz[i]) % MOD;
	ans = ans * inv2 % MOD;
	dfs2(T1.root);
	printf("%lld",(ans % MOD + MOD) % MOD);
}
posted @ 2023-11-01 21:35  ricky_lin  阅读(13)  评论(0)    收藏  举报