2024-3-24湖南多校部分题解A-E+G

2024-3-24 湖南多校部分题解A-E+G

本场搬自Pacific NorthWest February,24 2024
(便乘一波国科大搬题思路\乐)
链接http://www.acmicpc-pacnw.org/results.htm

A.ABC_String

ABC三个字母一组,从前往后遍历,记录遍历过程中开的最大组数即可。
比如ABABCC
ABAB时刻,开了2组,AB与AB,
ABABC 时有一组AB已经完成,只剩下一组AB。
看代码比较直观。

点击查看代码
#include<bits/stdc++.h> 
#define ll long long 
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
int a,b,c,ab,ac,bc,all;
char s[maxn];
signed main()
	{	 
		scanf("%s",s);int n=strlen(s);
		a=b=c=ab=ac=bc=all=0;int mx=0;
		for(int i=0;i<n;++i){
			if(s[i]=='A'){
				if(bc) --bc,--all;
				else if(b) --b,++ab;
				else if(c) --c,++ac;
				else{
					++a;++all;mx=std::max(all,mx);
				}
			}
			else if(s[i]=='B'){
				if(ac) --ac,--all;
				else if(a) --a,++ab;
				else if(c) --c,++bc;
				else{
					++b;++all;mx=std::max(all,mx);
				}
			}
			else{
				if(ab) --ab,--all;
				else if(a) --a,++ac;
				else if(b) --b,++bc;
				else{
					++c;++all;mx=std::max(all,mx);
				}
			}
		}
		assert(all==0);
		printf("%d\n",mx);
		return 0;
	}

B.ACceptable Seating Arrangements

从小到大把一个数放到其应到的位置。
举几个例子感受一下
起始数组与结束数组

\[ \begin{matrix} 1&2&3& &3&6&9\\ 4&5&6&->&2&5&8\\ 7&8&9& & 1&4&7 \end{matrix} \]

首先把数字\(1\)移动到应该在的位置。

\[swap(<1,3>,<3,1>),swap(<1,2>,<3,1>)swap(<1,1>,<3,1>) \]

\[ \begin{matrix} 1&2&3& &1&2&7& &1&3&7& &2&3&7\\ 4&5&6&->&4&5&6&->&4&5&6&->&4&5&6\\ 7&8&9& & 3&8&9& &2&8&9& &1&8&9 \end{matrix} \]

再把数字\(2\)移动到应该在的位置

\[swap(<1,2>,<2,1>),swap(<1,1>,<2,1>) \]

\[ \begin{matrix} 2&3&7& &2&4&7& &3&4&7\\ 4&5&6&->&3&5&6&->&2&5&6\\ 1&8&9& & 1&8&9& &1&8&9 \end{matrix} \]

所以,规律如下
假设我们要移动的值为S,所有小于S的值都已经在应有的位置上了,把这些小于S的值都用X表示。
显然,对每一行而言,X都在最左边,所有非X值都在X右边。因为只有X小于S所以S一定在X相邻的右侧,同理的,S要到达的位置,也一定在X相邻右侧,记这个位置上的数为T

\[ \begin{matrix} X&X&...&S&...\\ X&X&...&T&.... \end{matrix} \]

假设从S往后第一个大于T的数记为M(可能不存在,此时N为行末值),M前面的数记为N(可能\(S=N\))

\[ \begin{matrix} X&X&...&S&...&N&M&...\\ X&X&...&T&.... \end{matrix} \]

那么

\[for(int\ i=X_N;i>=X_S;--i)\ swap(<i,Y_S>,<X_T,Y_T>) \]

正确性自己想一下应该就明白了。
接下来放代码

点击查看代码
#include<bits/stdc++.h> 
#define ll long long 
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
#define pii std::pair<int,int>
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
int r,c;
int ar[505][505];
pii pos[505],ori[505];
struct msg{
	int x1,y1,x2,y2;
	msg(){}
	msg(int x1_,int y1_,int x2_,int y2_){
		x1=x1_;y1=y1_;x2=x2_;y2=y2_;
	}
};
std::vector<msg> ans;
void change(int x1,int y1,int x2,int y2){
	//printf("%d %d %d %d\n",x1,y1,x2,y2);
	ans.push_back(msg(x1,y1,x2,y2));
	int c1=ar[x1][y1],c2=ar[x2][y2];
	std::swap(ori[c1],ori[c2]);
	std::swap(ar[x1][y1],ar[x2][y2]);
}
void solve(int x){
	if(ori[x]==pos[x]) return;
	int ro=ori[x].first,co=ori[x].second;
	int rn=pos[x].first,cn=pos[x].second;
	//printf("%d:<%d,%d>-><%d,%d>\n",x,ro,co,rn,cn);
	int ct=co+1;
	while(ct<=c&&ar[ro][ct]<ar[rn][cn]) ++ct;
	//printf("ct=%d\n",ct);
	for(int i=ct-1;i>=co;--i) change(ro,i,rn,cn);
}
signed main()
	{	 
		r=Read(),c=Read();
		for(int i=1;i<=r;++i){
			for(int j=1;j<=c;++j){
				int x=Read();
				ori[x]=pii(i,j);
				ar[i][j]=x;
			}
		}
		for(int i=1;i<=r;++i){
			for(int j=1;j<=c;++j){
				int x=Read();
				pos[x]=pii(i,j);
			}
		}
		ans.clear();
		for(int i=1;i<=r*c;++i) solve(i);
		printf("%d\n",ans.size());
		for(auto it:ans) printf("%d %d %d %d\n",it.x1,it.y1,it.x2,it.y2);
        return 0;
	}	

C.Candy factory

我不知道出题人怎么想的,有\(O(1)\)解法的题目开5000的范围而且时限2s。我改成1s了。
我猜这题会被签烂。
随便说一下解法
题目问的是我们要追加多少糖果,那我们直接求我们至少要定做几袋糖果MX。记\(SUM\)为已有的糖果总数。\(ans=MX*K-SUM\)

  1. \(mx\)为单种类糖果中的最大数量。显然要有\(MX>=mx\),不然抽屉原理表明至少有一个袋子里会有重复的糖果。
    而如果\(MX>=mx\)显然每一颗糖果都能得到有效利用。
    可以把放糖果当做填充\(MX行K列\)的矩阵。要求每一行不出现相同糖果。那从第一列开始,每一列从上往下填充,填充完了放下一列。只要\(mx<=MX\)显然不会出现某一行出现同一种糖果。
  2. \(MX*K>=SUM\)不能说糖果数太多放不进去。

所以\(MX_{min}=MAX(mx,\lceil SUM/K \rceil)\)
最后输出 \(ans=MX_{min}*K-SUM,O(1)\)复杂度笑拉了。
水一下代码。

点击查看代码
#include<bits/stdc++.h> 
#define ll long long 
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
signed main()
	{	 
		int n=Read(),k=Read();
		ll sum=0,mx=0;
		for(int i=1;i<=n;++i){
			ll a=Read();mx=std::max(mx,a);
			sum+=a;
		}
		mx=std::max(mx,(sum-1)/k+1);
		//printf("%lld %lld\n",mx,sum);
		printf("%lld\n",mx*k-sum);
		return 0;
	}	

D.Cramming for Finals

没啥思维含量。
一个人影响的范围最多\(2*d\)列,每一列影响的范围又是一个区间。直接上差分。最多\(4*d*n\)条记录,能过。结束。
不过我记得给的std里面bowen.cpp表现吊打我,大家有兴趣可以看这个std。
放代码。(我代码写的烂,请谅解)

点击查看代码
#include<bits/stdc++.h> 
#define ll long long 
#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
typedef int LL;
const signed maxn=(signed)1e6+5;
const signed maxm=(signed)2e7+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
int r,c,d,n;
int xr[maxn],yr[maxn];
std::map<int,int> mp;
struct Msg{
	int pos,f;
	Msg(){}
	Msg(int a,int b){
		pos=a,f=b;
	}
}msg[maxm];
bool operator < (Msg a,Msg b){
	if(a.pos!=b.pos) return a.pos<b.pos;
	return a.f<a.f;  
}
int cnt=0;
signed main()
	{	 
		r=Read(),c=Read(),d=Read(),n=Read();
		for(int i=1;i<=n;++i){
			yr[i]=Read(),xr[i]=Read();
		}
		for(int i=1;i<=n;++i) mp[(xr[i]-1)*r+yr[i]]=1;
		//printf("r=%d c=%d\n",r,c);
		for(int i=1;i<=n;++i){
			int L=std::max(1LL,xr[i]-d),R=std::min(c,xr[i]+d);
			for(int j=L;j<=R;++j){
				int len=std::sqrt(d*d-(xr[i]-j)*(xr[i]-j));
				int up=std::max(1LL,yr[i]-len);//up<=down
				int down=std::min(r,yr[i]+len);
				//printf("poi=%d row=%d up=%d down=%d\n",i,j,up,down);
				//printf("+%d -%d\n",(j-1)*r+up,(j-1)*r+down+1);
				msg[++cnt]=Msg((j-1)*r+down+1,-1LL);msg[++cnt]=Msg((j-1)*r+up,1LL);
			}
		}
		msg[cnt+1]=Msg(r*c+1,0LL);
		std::sort(msg+1,msg+1+cnt);
		int mn=n,now=0;
		int pos,posn=msg[1].pos;
		for(int i=1;i<=cnt;++i){
			int f=msg[i].f;
			pos=posn;
			posn=msg[i+1].pos;
			now+=f;
			//printf("pos=%d posn=%d f=%d now=%d mn=%d,avipos=%d\n",pos,posn,f,now,mn,avi[pos]);
			// if(posn!=pos&&posn!=pos+1){
			// 	printf("0\n");exit(0);
			// }
			if(posn!=pos){
				if(!mp[pos]) mn=std::min(now,mn);
			}
		}
		//if(nr!=r||nc!=c) printf("0\n");
		printf("%lld\n",mn);
		return 0;
	}	

E.Eccentric Excursion

本场防AK题。我没看std和题解,一开始看错题目白干一下午,后来想出了个大概但是细节全错。拿着官方数据和随机生成数据对拍了一天,拍的头昏眼花才写出来。一看行数是std的两倍,而且也没比std快。直接泪流满面。要是没兴趣看我乱搞的话直接看官方题解吧。

题目翻译

给定一棵树,大小为\(n\)树上的点\(1\) ~ \(n\)编号,再给定一个\(k\)\(1<=k<n<=500\)要求你给出一个字典序最小的,长度为\(n\)的排列P,满足$$\sum_{i\in[1,n-1]}[<P_i,P_{i+1}>\notin E] =K,(E是边集)$$
说人话就是恰好有K个相邻的点对在树上没有连边。
如果不存在输出-1。

我的解法

首先字典序最小直接考虑贪心。
\(n\in[1,500]\)那么\(O(n^3)\)可以接受。
每一位从小到大枚举每一个数,再check如此前缀下是否可行。我们需要\(O(n)\)的check

check思路

考虑固定前缀下能够做到的k的最大值\(k_{max}\)和最小值\(k_{min}\)。任何\(k \in[k_{min},k_{max}]\)都可以得到。

  • 证明一下
    假设 \(k_{min}\)对应的序列为\(P_{min}\),\(k_{max}\)对应的序列为\(P_{max}\).我们通过一种一次只会使\(k\)最多变动\(1\)的操作将\(P_{min}\)操作到\(P_{max}\),这样操作过程中的所有序列就会包括区间中的所有k。那么合法操作有reverse \(P[1:x]\)或者 reverse \(P[x:n]\)。只有边界\(<P_x,P_{x+1}>\)会变化,其他的都只是移了位置。至于为啥这样可行,大家自己想一下,我已经有一个绝妙的证明方法,只是奈何空间不够~

假设我们在枚举第\(i\)个数,他的值是\(j\),那么\([1,j]\)的点对是否相连已经固定下来了,我们减去其中不相连的点对数量,余下的记为\(k_{i,j}\),要使的\([P_i(j),P_{i+1},...,P_n]\)中可能的k范围\([k_{min},k_{max}] \ni k_{i,j}\)
对于计算K,我的想法是,先考虑\([P_{i+1},...,P_n]\)的K的范围,若是能在凑到\(k_{max}\)的情况中有满足\(<P_i,P_{i+1}> \notin E\)情况的,就\(++k_{max}\)\(k_{min}\)亦然。

那么先考虑\([P_{i+1},...,P_n]\)的K的范围,这些点构成了一个子图(不必连通),只要这个子图不是星型图(一个点连着其他所有点)(单点不算星图,两个相连的点算),也就是任何一个点都有一个其没有连接的点,那么这张图能提供的\(k_{max}=|V|-1(V是点集).\)如果是星型图就是\(k_{max}=|V|-2.\)
对于星型图很好理解\([叶子结点,叶子,...,唯一非叶节点]\)就凑出来\(|V|-2\)了。
对于非星型图,我需要数学归纳法证明,而且是和另一个命题联合证明。

  • 数学归纳法部分

    • 假设:对于任意非星图,V为其点集,都能找到使\(k=|V|-1\)的序列(第一个命题)。并且,任意非星图,再额外加上一个强制要求出现在序列第一位的点\(J\),只要\(J\)不与此非星图的所有点连有边,那么此点加非星图所构成的序列可以找到\(k=|V|\)的的情况(第二个命题)。

    • 归纳奠基:对于n较小的情况1(事实上连通图\(|V|>=4\)才有非星图),可以找到使得\(k=|V|-1的情况\)

    • 归纳递推:若\(|V|<=X\)时成立,那么|V|=X+1时也能成立。
      先证明第一个命题。由于第二个命题,我挑一个点作\(J\),因为非星图,\(J\)不可能和其他所有点连边,这样剩下的点集大小\(|V|-1<=X,|V|=X+1\)的第一个命题被\(|V|=X\)的第二个命题证明。
      接下来证明\(|V|=X+1\)的第二个命题。挑一个点出来记为\(J\),剩下的点可以凑出\(k=|V|-2\),记此时序列为\([P_1,P_2,...,P_{|V|-1}]\)(不包括\(J\)).若\(<J,P_1> \notin E\),直接就解决了。若是\(<J,P_{|V|-1}> \notin E\),直接 reverse \(P[1:|V|-1]\)首尾互换就解决了。
      若是没有解决,就比较麻烦,得先引入其它工具。

      • \(J\)直接相连的任意两点之间不会直接相连
        证明:树上无环,非常显然。
      • \(k=|P|-1\)时,就是任意相邻两点之间都没有连边,只要保持任意两点始终无连边,就可以保持\(k\)不变。

      先记\(rear=|P|-1\)接下来的所有操作都会使
      \((i>rear)\implies (<J,P_i>\in E)\)

      若是序列头不与\(J\)相连,直接证毕,结束。
      若是序列头与\(J\)相连,reverse\(P[1:rear]\),原序列头就会被移到rear上,但是\(rear\)之后的点都和\(J\)相连,所以\(<P_{rear},P_{rear+1}>\notin E\)\(k\)保持不变,没有缩小。之后再\(--rear\),仍然满足\((i>rear)\implies (<J,P_i>\in E)\)
      只要有一个点满足与点J不相连,子序列\(P[1:rear]\)一直在缩小,那个点迟早被甩到序列头上去,这样\(<J,P_1> \notin E\)满足,第二个命题成立。
      归纳递推证明完毕。

    由以上可得知两个命题正确.

请把数学归纳法里的P和J当做局部变量忘记,全局P是答案序列,\(J=P_i\)
通过第二个命题(第二个命题对于非星图也成立,也就是不全连就\(+1\)),我们已经知道固定序列头的情况下K的最大值 $$k_{max}=[\exists u \in[i+1,n](<J,P_u>\notin E)]+|n-i-1|-[P[i+1:n]为星图]$$

接下来考虑最小值,说实在的,我也没法严谨证明,但我觉得是对的。大家感性理解即可。

  • 额外提一下,序列对\(k\)的贡献就是序列里链的数量-1(链就是最长的,满足区间内相邻的点都有连边的区间)

首先为了使\(k\)最小,我们最好一个一个联通块的做,这样总是不劣的。
然后原图就是一个树,一个联通块也只能是一个树,那么我考虑从叶子结点往上伸展,看看能通过连边伸展到什么程度,如果只有一条链直接两个叶子结点伸展相遇就结束了,但要是有分叉,两个或多个叶子结点伸展中在某一点相遇,就比较麻烦,我的考量是选择两个叶子结点伸展的链合并,其他的因为占用不了相交点就只能单做一条链。出于这个考量就可以使用拓扑排序解决。

但是还有一个问题,当我固定\(P_i=J\)时,能否使得不增加\(k\)的情况下,使\(<P_i,P_{i+1}>\in E\),如果可以,\(k_{min}\)保持,否则\(++k{min}\)
这个问题我是这么考量的,每一个联通块最多有一个点和\(J\)相连,我直接先把\(J\)当做叶子结点挂在相连的点上,再做一遍拓扑,若是答案不增就可以保持。若是所有与\(J\)相连的联通块挂上\(J\)都是使答案增加,那么只能\(++k_{min}\)

到此所有思路解释完毕。
md感觉好冗长,到时候写的肯定是一坨屎山。
事实上我的代码长度是std两倍。泪目了。
我一段一段的放代码。

//建边部分
bool adj[maxn][maxn];//邻接表存边
struct Edge{
	int to,nxt;
}edge[maxn<<1];
int head[maxn],ecnt,du[maxn];
void Adde(int u,int v){//再加上前向星存边
	edge[++ecnt]=(Edge){v,head[u]};
	head[u]=ecnt;++du[u];
}
//加入和删除答案
bool vis[maxn];//1 means in ans,0 in graph
void add(int u){//加入图,从ans中del
	--ans_ptr;vis[u]=0;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(vis[v]) continue;
		++du[u];++du[v];
	}
}
void del(int u){//从图中del,加入ans
	ans[++ans_ptr]=u;vis[u]=1;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(vis[v]||du[v]==0) continue;
        //printf("del %d-%d\n",u,v);
		--du[u];--du[v];
	}
}
//切分连通块
int blc[maxn],siz[maxn],bcnt;
//blc 点属于哪个联通块,siz 联通块大小 bcnt联通块数目
int vec[maxn][maxn];//记录每块联通块有哪些点
bool dvis[maxn];//记录是否入ans或者计入连通块
void devide(){//把图分为若干连通子图
	bcnt=0;
	for(int i=1;i<=n;++i) dvis[i]=vis[i];
	for(int i=1;i<=n;++i){
		if(!dvis[i]) unite(i,++bcnt);
	}
}
void unite(int x,int bc){//标记整块连通子图
	std::queue<int> q;
	q.push(x);dvis[x]=1;
	siz[bc]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		vec[bc][++siz[bc]]=u;
		blc[u]=bc;
		for(int i=head[u];i;i=edge[i].nxt){
			int v=edge[i].to;
			if(dvis[v]) continue;
			dvis[v]=1;q.push(v);
		}
	}
}
//拓扑排序
int fa[maxn],ch[maxn][2];
//fa就是树上父亲,ch记录有那些点伸展到此点
int tp[maxn];//top_sort
//tp模仿du,但是du属于原图,不能动,所以copy一份
int tops[maxn],tcnt;
//拓扑完的序列
void top_sort(int ver){//ver是联通块下标
	std::queue<int> q;
	for(int i=1;i<=siz[ver];++i){
		int u=vec[ver][i];
		tp[u]=du[u];fa[u]=0;
		if(tp[u]==1) q.push(u);
	}
	tcnt=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		tops[++tcnt]=u;
		for(int i=head[u];i;i=edge[i].nxt){
			int v=edge[i].to;
			if(tp[v]==0||vis[v]) continue;
			--tp[u];--tp[v];fa[u]=v;
			if(tp[v]==1) q.push(v);
		}
	}
}
//求最小值
int solve_tree(){
	//将一棵树分为最少的链,数量为temp
    //由single_tree()作初始化
	int temp=0;
	for(int i=1;i<=tcnt;++i){
		int v=tops[i];
		int u=fa[v]; 
		if(ch[v][1]) ++temp;
		else{
			if(!ch[v][0]) ch[v][0]=v;
			if(i==tcnt) ++temp;
			else if(ch[u][1]) ++temp;
			else if(ch[u][0]) ch[u][1]=ch[v][0];
			else ch[u][0]=ch[v][0]; 
		}
	}
	return temp;
}
void single_tree(int ver,int &mn,int poi,bool &ex){
	//求单个图(树)对k的最小贡献
    //ex 就是是否要++Kmin
	if(siz[ver]<=2){
		++mn;
		for(int i=1;i<=siz[ver];++i){
            if(adj[poi][vec[ver][i]]) ex=0;
        }
		return;
	}
	top_sort(ver);
	int poi_adj=-1;//与P_i相连的点
	for(int i=1;i<=tcnt;++i){
		ch[tops[i]][0]=ch[tops[i]][1]=0;
		if(adj[poi][tops[i]]) poi_adj=tops[i];
	}
	int temp=solve_tree();
	mn+=temp;
	if(ex&&poi_adj!=-1){
        //看看挂P_i是否会增加答案
		for(int i=1;i<=tcnt;++i)
			ch[tops[i]][0]=ch[tops[i]][1]=0;
		ch[poi_adj][0]=poi;//poi_adj下挂一个叶子结点poi
		if(solve_tree()==temp) ex=0;
	}
}
//求最大值和调用求最小值
void get(int &mx,int &mn,int poi){
	if(bcnt==0){
		mn=mx=1;return;
	}
	if(bcnt==1&&siz[1]==1) mx=2-adj[poi][vec[1][1]];
	else{
		mx=0;
		for(int i=1;i<=bcnt;++i) mx+=siz[i];
		for(int i=1;i<=n;++i){
			if(!vis[i]&&du[i]==mx-1){
				--mx;break;
			}
		}
        for(int i=1;i<=n;++i) 
			if(!vis[i]&&!adj[poi][i])
			{++mx;break;}
	}
	bool ex=1;mn=0;
	for(int i=1;i<=bcnt;++i) single_tree(i,mn,poi,ex);
	mn+=ex;
}
//主程序
void pfno(){
	printf("-1\n");exit(0);
}
void pfans(){
	for(int i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);exit(0);
}
signed main(){
	n=Read(),k=Read()+1;
	for(int i=1;i<n;++i){
		int u(Read()),v(Read());
		adj[u][v]=adj[v][u]=1;
		Adde(u,v);Adde(v,u);
	}
	for(int i=1;i<=n;++i) blc[i]=1;
	for(int i=1;i<=n;++i) vec[1][i]=i;
	bcnt=1;siz[1]=n;
	int mx,mn;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(vis[j]) continue;
			if(i>1&&!adj[ans[i-1]][j]) --k;
			del(j);devide();
			get(mx,mn,j);
			if(k>=mn&&k<=mx) break;
			if(i>1&&!adj[ans[i-1]][j]) ++k;
			add(j);
		}
		if(ans_ptr!=i) pfno();
	}
	pfans();
}

完结撒花

A题秒了
B题也没啥水平,但我其实卡了好久
C题也卡了好一会,但是\(O(1)\)多少有点搞笑
D题我跑的比std慢真得泪目。
不知道有没有人注意我代码里面的return \(a.f<a.f\);
E题写的真屎。

加时赛之别人写的G

某个不会用markdown的发了我一份word题解,太帅了。
我给他补补markdown。以下为题解。

由条件4可知,相邻两行存在约束关系,那么考虑DP

\(f( r_i, c_l, c_r)\)表示从第一行操作到第\(r_i\) 行,且第 \(r_i\) 行中的 1 所在的列号为 \(c_l\)\(c_r\) 时的最少操作数
若直接进行转移, 则时间复杂度为 \(O(r*c^4)\)这显然是不够的。
观察转移过程可发现,每次转移求得都是\(f(r_i – 1, c_l, c_r)\)的二维前缀最小值。 只要我们将该值提前算出来,那么就可以实现\(O(1)\)的转移,此时的时间复杂度为\(O(r * c^2)\),在最坏情况下依然无法通过。
观察可知,原矩阵在转置后所得新矩阵满足条件时,原矩阵也满足条件。那么在 \(c > r\) 时,将原矩阵转置在进行DP即可,此时的时间复杂度为 \(O(max(r, c) * min(r, c) * min(r, c)) = O(r * c * min(r, c))\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;

void Sol()
{
    int r, c;
    cin >> r >> c;
    vector<string> matrix(r);
    for (int i = 0; i < r; i++)
        cin >> matrix[i];
    if (r < c)
    {
        vector<string> trans(c);
        for (int i = 0; i < c; i++)
        {
            trans[i].assign(r, '0');
            for (int j = 0; j < r; j++)
                trans[i][j] = matrix[j][i];
        }

        matrix = move(trans);
        swap(r, c);
    }

    vector<vector<int>> dp(c, vector<int>(c, INF));
    vector<int> pre(c + 1);
    for (int i = 0; i < r; i++)
    {
        for (int j = 0; j < c; j++)
        {
            pre[j + 1] = pre[j] + (matrix[i][j] - '0');
        }

        vector<int> predp = dp[0];
        vector<vector<int>> ndp(c, vector<int>(c, INF));

        for (int x = 0; x < c; x++)
        {
            for (int y = x; y < c; y++)
            {
                predp[y] = min(predp[y], dp[x][y]);
            }

            int minpre = predp[x == 0 ? 0 : x - 1];
            for (int y = x; y < c; y++)
            {
                minpre = min(minpre, predp[y]);
                if (i == 0)
                    minpre = x == 0 ? 0 : INF;
                ndp[x][y] = min(ndp[x][y], pre[x] + (y - x + 1) - pre[y + 1] + pre[x] + pre[c] - pre[y + 1] + minpre);
            }
        }

        dp = move(ndp);
    }

    int ans = INF;
    for (int i = 0; i < c; i++)
        ans = min(ans, dp[i][c - 1]);
    cout << ans << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    int t = 1;
    //cin >> t;
    while (t--)
    {
        Sol();
    }

    return 0;
}
posted @ 2024-03-21 21:32  LOOP_0  阅读(270)  评论(0)    收藏  举报