Codeforces Round #675 (Div. 2) Solution

本文介绍了算法竞赛中的策略和常见问题。包括如何构造四边形的第四条边、矩阵回文行列的最少操作次数、商品价格的砍价问题、二维平面上的最短路径规划以及字符串后缀的字典序最小解法。通过实例解析了动态规划、回文判断、区间最大公约数等算法的应用,并展示了高效的数据结构和优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

比赛的时候做题顺序为 A B C F ABCF ABCF,由于 F F F的做法太傻,导致调了有点久,没有时间做 D D D了.
比赛刚结束的时候,排在 o f f i c i a l official official r k 44 rk44 rk44.
结果一觉醒来,别人的 E E E大部分都被 h a c k hack hack了…
我居然上了 r k 5 rk5 rk5(大雾…

在这里插入图片描述
妈妈我上首页啦

A A A

给定3条边.让你再造一条边使得可以围成4边形.

设三条边分别为 a , b , c a,b,c a,b,c,那么 a + b + c − 1 a+b+c-1 a+b+c1为第4边.

B B B

给定一个 n ∗ m n*m nm的矩阵.
每次只能让一个值变化1.
问至少多少次操作才能使得每行每列都回文.

根据回文的定义,
矩阵内有3种情况:
1 / 2 / 4 1/2/4 1/2/4个数要相等.

我们只要找到数的中位数就好.

int n,m;
ll ans,a[110][110];
 
void solve() {
	qr(n); qr(m); ans=0;
	rep(i,1,n) rep(j,1,m) qr(a[i][j]);
	rep(i,1,(n+1)/2)
		rep(j,1,(m+1)/2) {
			vector<ll> cur={a[i][j],a[n-i+1][j],a[i][m-j+1],a[n-i+1][m-j+1]};
			sort(all(cur));
			ll t=cur[1];
			if(i*2-1 == n&&j*2-1==m) ;
			else if(i*2-1 == n|| j*2-1 ==m) ans+=cur[2]-cur[1];
			else rep(k,0,3) ans += abs(t-cur[k]);
		}
	pr2(ans);
}

C C C

给定一个商品的价格 n ( lg ⁡ n ≤ 1 e 5 ) n(\lg n\le 1e5) n(lgn1e5).
你是一个砍价高手,你可以删除任意一个非空连续子段,删除后两部分相接.
老板说,如果你能算出所有情况下的价格总和 m o d    p \mod p modp,那么商品就送你了~~

定义 l e n len len为数位总数.
我们考虑删除一个区间 [ l , r ] [l,r] [l,r]的贡献:
n u m = f ( 1 , l − 1 ) ∗ 1 0 l e n − r + f ( r + 1 , l e n ) num=f(1,l-1)*10^{len-r}+f(r+1,len) num=f(1,l1)10lenr+f(r+1,len).
其中 f ( i , j ) f(i,j) f(i,j) [ i , j ] [i,j] [i,j]形成的数.

那么我们只要拆开两个部分分别贡献即可.

int n;
char str[N];
ll x,ans,s[N],p[N];
 
void solve() {
	x=ans=0;scanf("%s",str+1); n=strlen(str+1);
	p[0]=s[0]=1; 
	rep(i,1,n) p[i]=p[i-1]*10%mod,s[i]=(s[i-1]+p[i])%mod,str[i] -= '0';
	rep(i,1,n-1) {
		x=(x*10+str[i])%mod;
		ad(ans,x*s[n-i-1]%mod);
	}
	x=0;
	REP(i,1,n) {
		x=(x+str[i]*p[n-i])%mod;
		ad(ans,x*(i-1)%mod);
	}
	 pr2(ans);
}

D D D

在2维平面内,给定起点和终点.
同时,还有 m m m个传送门,当你和任意一个传送门有相同的 x / y x/y x/y坐标时,你可以选择瞬移到该传送门的位置.(不花费时间)
与此同时,你可以选择往4个方向走一个单位,耗时1个单位.
问从起点到终点的最小花费.

d i j k s t r a dijkstra dijkstra即可.
一个显然的做法是要移动到传送门,我们只会改变 x / y x/y x/y.一定不可能一起改变.
所以,我们先从起点移动到任意传送门.
然后,从传送门互相转移.
因为我们只用移动 x / y x/y x/y,所以每个点只需向 x / y x/y x/y相邻的点连边即可.
最后再考虑从每个点移动到终点即可.

int n,m,u[N],v[N],d[N];
vector<pii> X,Y;
V<int> e[N];
 
void add(int x,int y) {
	e[x].pb(y);
	e[y].pb(x);
}
 
priority_queue<pii,vector<pii>,greater<pii> > q;
 
void insert(int x,int y) {
	int D=d[x]+min(abs(u[x]-u[y]),abs(v[x]-v[y]));
	if(d[y] > D) {
		d[y]=D;
		q.push(mk(D,y));
	}
}
 
void solve() {
	qr(n); qr(m); m += 2;
	rep(i,1,m) {
		qr(u[i]); qr(v[i]);
		if(i > 2) X.pb(mk(u[i],i)),Y.pb(mk(v[i],i));
	}
	sort(all(X)); sort(all(Y));
	rep(i,0,SZ(X)-2) {
		add(X[i].se,X[i+1].se);
		add(Y[i].se,Y[i+1].se);
	}
	memset(d,63,sizeof d);d[1]=0;
	rep(i,3,m) insert(1,i);
	while(q.size()) {
		int D=q.top().fi,x=q.top().se; q.pop();
		if(D>d[x]) continue;
		for(int y:e[x]) insert(x,y);
	}
	int ans=abs(u[1]-u[2])+abs(v[1]-v[2]);
	rep(i,3,m) cmin(ans,d[i]+abs(u[i]-u[2])+abs(v[i]-v[2]));
	pr2(ans);
}

E E E

给定一个长度为 n n n的串,然后对于每个后缀输出字典序最小的答案.
假如有 s i = s i + 1 s_i=s_{i+1} si=si+1,那么你可以选择删除两个字符.
删除完后,得到答案.
n ≤ 1 e 5 n\le 1e5 n1e5.

如果能删除就删除的话不一定是最优的,比如abbf的情况.
因为是后缀处理,所以我们从后往前求解.
如果 s i = s i + 1 s_i=s_{i+1} si=si+1,我们考虑删除和不删除哪个的字典序更小.
a n s i ans_i ansi表示后缀 i i i的答案.
那么删除就是 a n s i + 2 ans_{i+2} ansi+2,否则为 s i + s i + a n s i + 2 s_i+s_i+ans_{i+2} si+si+ansi+2.
我们当然不能 O ( n ) O(n) O(n)判断字符串大小.
实际上,我们只要知道 a n s i + 2 ans_{i+2} ansi+2的开头两个不同的字符即可.(可以尝试写几个串来感受一下).
然后,我们不需要直接求出 a n s i ans_i ansi,只用用指针指向去掉首字符后的位置即可.

int n,pos[N],tot,nxt[N];
char s[N],ch[N],str[N][2];
 
bool vis[N];
string ans[N]; int len[N];
 
void dfs(int x) {
	if(vis[x]) return ; vis[x]=1;
	dfs(nxt[x]); len[x]=len[nxt[x]]+1;
	char c=ch[x];
	ans[x]=c+ans[nxt[x]];
	if(len[x]>10) ans[x].erase(5,1);
	if(len[x] == 11) ans[x][5]=ans[x][6]=ans[x][7]='.';
}
 
void solve() {
	scanf("%s",s+1); n=strlen(s+1);
	pos[n+1]=tot=0; vis[0]=1;
	REP(i,1,n) {
		if(s[i] == s[i+1]) {
			pair<char,char>now;
			int x=pos[i+2];
			now=mk(str[x][0],str[x][1]);
			if(mk(s[i],s[i])<now) {
				pos[i]=++tot;
				nxt[tot]=pos[i+1];
				ch[tot]=s[i];
				if(s[i] == str[x][0]) str[tot][0]=str[x][0],str[tot][1]=str[x][1];
				else str[tot][1]=str[x][0],str[tot][0]=s[i];
			}
			else pos[i]=pos[i+2];
		}
		else {
			pos[i]=++tot;
			nxt[tot]=pos[i+1];
			ch[tot]=s[i];
			int x=pos[i+1];
			if(s[i] == str[x][0]) str[tot][0]=str[x][0],str[tot][1]=str[x][1];
			else str[tot][1]=str[x][0],str[tot][0]=s[i];
		}
	}
	rep(i,1,tot) dfs(i);
	rep(i,1,n) {
		int x=pos[i];
		pr1(len[x]);
		puts(ans[x].c_str());
	}
}

F F F

强制在线求区间 l c m lcm lcm.
n ≤ 1 e 5 , a i ≤ 2 e 5 n\le 1e5,a_i\le 2e5 n1e5,ai2e5.

比赛的时候想了一种很傻的笛卡尔树套树套树的做法.
这种做法劣在没有考虑到可以使用逆元,所以模数可以任意,但是复杂度较高.

实际上我们并不用建出笛卡尔树,只用存栈即可.
然后树套树可以用主席树代替.
其中主席树 r o o t [ r ] root[r] root[r]存的是,当右端点为 r r r时,各个左端点的答案.

显然长度越长, l c m lcm lcm非降.
对于一个质数 p p p而言就是指数从左往右下降.
我们只要仔细维护单调栈即可.
当出现当前位置的指数比前面一段的大的时候,我们撤销前面一段即可.
下面的代码实现是用差分搞的,这就要求一定要有逆元.
我们可以改为区间乘法,这样就不用要求逆元,复杂度不变.

复杂度为 O ( n log ⁡ n log ⁡ m a x a i ) O(n\log n\log maxa_i) O(nlognlogmaxai).其中 log ⁡ m a x a i \log maxa_i logmaxai表示的是质因子个数,所以这个常数较小.

int n,a[N],inv[N],mx,prime[N],tot,v[N],id[N];

struct node {int l,r,c; } tr[M]; int cnt,root[N];

int upd(int p,int l,int r,int pos,ll d) {
	int x=++cnt; tr[x]=tr[p]; tr[x].c=tr[x].c*d%mod;
	if(l == r) return x;
	int mid=(l+r)/2;
	if(pos<=mid) lc=upd(tr[p].l,l,mid,pos,d);
	else rc=upd(tr[p].r,mid+1,r,pos,d);
	return x;
}

int ask(int x,int l,int r,int pos) {//<=pos
	if(l>pos) return 1;
	if(r<=pos) return tr[x].c;
	int mid=(l+r)/2;
	return (ll)ask(lson,pos)*ask(rson,pos)%mod;
}

void solve() {
	qr(n); 
	rep(i,1,n) qr(a[i]),cmax(mx,a[i]);
	inv[0]=inv[1]=1; 
	rep(i,2,mx) {
		inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
		if(!v[i]) {
			v[i]=prime[++tot]=i;
			for(ll j=i;j<=mx;j *= i) id[j]=tot;
		}
		rep(j,1,tot) {
			int k=i*prime[j];
			if(k>mx) break;
			if(i%prime[j] ==0 ){v[k]=v[i]*prime[j]; break;}
			else v[k]=prime[j];
		}
	}
	V<V<pii> > sta(tot+1,{mk(0,0)});
	tr[0].c=1;
	rep(i,1,n) {
		root[i]=root[i-1];
		int x=a[i]; 
		while(x>1) {
			int y=v[x],p=id[y]; x /= y;
			while(sta[p].size() > 1 && y > sta[p].back().se) {
				int pos=sta[p].back().fi,z=sta[p].back().se; sta[p].pop_back();
				root[i]=upd(root[i],1,n,sta[p].back().fi+1,inv[z]);
				root[i]=upd(root[i],1,n,pos+1,z);
			}
			root[i]=upd(root[i],1,n,sta[p].back().fi+1,y);
			sta[p].emplace_back(i,y);
		}
		if(i<n) root[i]=upd(root[i],1,n,i+1,inv[a[i]]);
	}
	int l,r,ans=0,q; qr(q); while(q--) {
		qr(l); qr(r);
		l=(l+ans)%n+1;
		r=(r+ans)%n+1;
		if(l > r) swap(l,r);
		pr2(ans=ask(root[r],1,n,l));
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值