4.21考试题解

1131 Card Manager 题解
这题只有暴力乱搞和正解两种方法
??分解法
	乱搞
20分解法
	输出-1
80分解法
	考虑将卡牌看成点数字看成边
	发现没有什么特殊性质
	观察图片
	发现除了开头和结尾的两个数外
	其他数均出现了偶数次
	这像极了欧拉回路中的点度
	继续往欧拉回路上想
	发现可以以数字为点,以卡片为边建图
	问题就变成了输出一张图的欧拉回路/欧拉路径
	(欧拉回路见https://baike.baidu.com/item/%E6%AC%A7%E6%8B%89%E5%9B%9E%E8%B7%AF/10036484?fr=aladdin)
	什么时候有解呢
	若一张图联通且任意点度数为偶则存在欧拉回路
	若只有两个点度数为奇则可以以这两点为首尾形成欧拉路径
	所以无解仅当图不联通或超过两点度数为奇
	有解怎么输出呢
	有一个算法叫fleury可以求
	但是感觉很那个算法写的很冗杂所以不推荐
	考虑下面的做法
	1.若所有点度数为偶
		给图中每条边加一个是否走过的标记
		随意选一个点开始搜
		只走没走过的边
		可以证明若无路可走了一定是回到了原点
		回到原点后依次输出每一步访问的点(一个点可能被访问多次)
	2.若有两点度数为奇
		和上面一样从其中一点开始搜
		无路可走时一定是在另一点
		同样,输出路径
	上面的做法有一个小问题
	它不一定遍历完了所有的边
	因为无路可走之时往后退几步就有可能有路
	怎么改进?
	上面我们用的是dfs进栈序并且无路可走就不搜了相当于只找到了一个环/路径
	正解使用的是dfs退栈序并且无路可走之时退栈继续搜相当于把多个环接起来(详见代码)
	相当于是从终点开始搜到起点
	正解是怎么把多个环拼接在一起的?
	
	首先从起点开始搜到终点然后回退,每退一步输出退之前所在的点
	退到某个点(设其为A点)发现又有路可走则沿该路又向前搜
	则什么时候又会停下来呢,当再一次无路可走时就停下来了
	可以证明再次无路可走时一定在A点
	此时回退并输出点就相当于把A所在的一个小环与原来的大环相接
	当然还有可能环套环套环,递归会解决它的
	问题解决了
100分解法
	为什么上面只有80分?
	考虑上面的复杂度可能会达到O(m^2)
	于是加个当前弧优化就可以做到O(m)
	相当于把走过的边删掉
	最后
	考虑到m为1e6
	则可能会递归m层
	可能会爆栈(然而实际上并没有爆)
	如果爆栈就像代码里一样手写一个dfs就好了
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
vector<int>vec[N];
struct edge{int v,vis,nxt;}e[N*2];
int n,m,st,sz,cnt,d[N],hd[N],z[N*2];
struct Q{int x,y;}q[N];
vector<int>ans;
void adde(int u,int v){e[++cnt]=(edge){v,0,hd[u]},hd[u]=cnt;}
int tp,stk[N];
void dfs(int u){
	stk[++tp]=u;
	while(tp){
		u=stk[tp];
		int &i=hd[u];
		while(i&&e[i].vis)i=e[i].nxt;
		if(i){
			e[i].vis=e[i^1].vis=1;
			stk[++tp]=e[i].v;
			continue;
		}
		ans.push_back(z[u]);
		tp--;
	}
}
int main()
{
	cnt=1;
	cin>>m;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].x,&q[i].y);
		z[++sz]=q[i].x,z[++sz]=q[i].y;
	}
	sort(z+1,z+1+sz);
	sz=unique(z+1,z+1+sz)-z-1;
	random_shuffle(q+1,q+1+m);
	for(int i=1;i<=m;i++){
		Q &qy=q[i];
		qy.x=lower_bound(z+1,z+1+sz,qy.x)-z;
		qy.y=lower_bound(z+1,z+1+sz,qy.y)-z;
		adde(qy.x,qy.y),adde(qy.y,qy.x);
		d[qy.x]++,d[qy.y]++;
	}
	st=1;
	int tot=0;
	for(int i=1;i<=sz;i++)if(d[i]&1)st=i,tot++;
	if(tot>2)return puts("-1"),0;
	dfs(st);
	if(ans.size()!=m+1)return puts("-1"),0;
	for(int i=0;i<ans.size();i++)printf("%d ",ans[i]);
	return 0;
}
1130 Segment Manager 题解
20分解法
	按题意O(n^3)dp即可
60分解法
	斜率优化入门题O(n^2)
100分解法
	先说40分解法
	假设原序列确定
	将整个序列分成了m段
	那么随着m增大答案单调不升
	并且答案的变化率也单调不升
	即答案关于m的函数的斜率恒为负且斜率单调不升
	这形成了一个凹函数(即(f(x)+f(y))/2>=f((x+y)/2))
	考虑这样一种做法
	我们在20分做法里
	用dp[i][j]表示到i位选了j段的最小价值
	那么我们把第二位去掉,即去掉段数限制
	那么这个dp由O(n^3)变为了O(n^2)
	此时这个dp求出来的是什么
	显然由于答案单减
	求出来的是每个数各自一段的总价值,即为0
	考虑这样一种处(qi)理(ji)方(yin)法(qiao)
	给选择的每一段强行加上一个额外费用w
	那么选k段就会有额外k*w的费用
	此时答案就不一定是m==n时最优了
	换句话说
	答案关于m的函数的右端被台升了
	及函数的最小值处的横坐标左移了
	假设左移后恰好在题中所求的m处最优
	那么就可以直接用这种dp求出答案再减去m*w就行了
	那我怎么知道w应该取多少m才是最优的?
	我不知道,所以二分w
	每二分一次就dp算一次最优解并记录此时取了多少段(设有k段)
	若k<m,w应该调小
	反之应该调大
	复杂度O(n^2*logn)
	那么100分解法就是把40分解法中的暴力dp换成斜率优化就行了
	复杂度O(nlogn)
	这种二分好像叫wqs二分(wqs是谁)
	很实用但用的人却不多
	另外推荐cf739E
	可以用wqs套wqs做到比正解优的O(n*(logn)^2)
细节
	wqs二分一定能使最后恰好在m处最优吗
	不一定,可能并列最优
	那如果二分到最后也不是m最优,即l,r中有一个的最优取值与m并列
	我怎么知道是l,r中的哪一个?
	可以使用代码中的处理方法:
	假设m与r的最优决策点的dp值相等
	而不是l的最优决策点
	那么用l算出来的m的实际dp值一定优过头了
	即我们应该取l和r中在m点算出来不太优的那个
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int N=5.1e5,len=N*3;
char str[len],*p=str;
int read(){
	int x=0;
	while(*p<'0')++p;
	while(*p>='0')x=10*x+*p++-'0';
	return x;
}
int n,m,q[N],cnt[N];
ll mid,s[N],_s2[N],_2s[N],dp[N],x[N],y[N];
bool cal(int k,int j,int i){
	return y[j]-y[k]<s[i]*(x[j]-x[k]);
}
bool cal2(int k,int j,int i){
	return (y[j]-y[k])*(x[i]-x[j])>(y[i]-y[j])*(x[j]-x[k]);
}
int Dp(){
	int h=1,t=0;
	q[++t]=0;
	for(int i=1;i<=n;i++){
		while(h<t&&cal(q[h],q[h+1],i))++h;
		int j=q[h];
		dp[i]=dp[j]+mid+(((s[i]-s[j])*(s[i]-s[j])-(_2s[i]-_2s[j]))>>1);
		cnt[i]=cnt[j]+1;
		y[i]=(dp[i]<<1)+_s2[i]+_2s[i];
		x[i]=s[i]<<1;
		while(h<t&&cal2(q[t-1],q[t],i))--t;
		q[++t]=i;
	}
	return cnt[n];
}
int main()//_2s为平方的和,_s2为和的平方
{
	fread(str,1,len,stdin);
	n=read(),m=read();
	for(int i=1;i<=n;i++)s[i]=read(),_2s[i]=_2s[i-1]+s[i]*s[i],s[i]+=s[i-1],_s2[i]=s[i]*s[i];
	ll l=0,r=_s2[n];
	while(l<=r){
		mid=(l+r)>>1;
		int ret=Dp();
		if(ret<m)r=mid-1;
		else if(ret>m)l=mid+1;
		else l=mid,r=mid-1;
	}
	mid=l,Dp();
	ll ans1=dp[n]-mid*m;
	mid=r,Dp();
	ll ans2=dp[n]-mid*m;
	cout<<max(ans1,ans2)<<endl;
	return 0;
}
1129 Matrix Manager 题解
建议不会正解也要写一写30分解法
30分解法
	怎么办一看就是毒瘤数据结构
	我会分块!
	将矩阵分为一个个的正方形
	设正方形边长为B
	则操作大块的复杂度为O((n/B)^2)
	操作块内的复杂度为O(n*B)
	即共O((n/B)^2+n*B)
	当B==n^(1/3)时取得最优
	总复杂度O(q*n^(4/3))
	其实30分可以做到q==1e5
	因为打了一下发现0.5s就过了而给了10s
无撤销操作的60分
	即需要支持平面加法平面求和
	可以用:
		1.线段树套线段树
		2.区间bit套线段树
		3.区间bit套区间bit(二维bit)+hash表
	推荐第二种
	第一种常数较大并且可能内存吃不消
	而且如果非要写线段树套线段树的话
	是不能用lazy标记的(至少外层线段树不能用)
	因为lazy无法下放
	所以非要写第一种就用永久化标记吧,常数小并且还好写一些
100分解法
	直接撤销一来不好做二来复杂度承受不起因为会不停的跳来跳去
	那么可以想到一道题:
	https://www.luogu.org/problemnew/show/P1383
	难道要写二维主席树之类的恶心玩意?
	不用
	首先我们给每一个非询问操作一个标号
	每个标号就对应了一种版本(类似主席树)
	设当前标号为id
	每一个修改操作都是以上一个版本(id-1)为基础新建一个版本
	那么一个撤销操作相当于是以(id-a-1)号版本为基础新建一个版本
	所以还是要主席树?
	不用
	我们称以i号版本为基础构建的版本为i的儿子
	则所有版本构成了一棵树
	将这棵树建出来
	按dfs的方式遍历
	每进入一个修改节点就修改
	退出一个修改节点就反向修改
	那我们就可以在dfs的过程中回答每个节点包含的询问
	就避免了主席树
代码
	给出的是第三种做法
#include<cstdio>
using namespace std;
const int len=1<<22,N=1.1e5,Mod=19999999;
typedef long long ll;
struct IO{
	char sr[len],sw[len],stmp[25],*pr,*pw,*ptmp;
	IO(){fread(sr,1,len,stdin),pr=sr,pw=sw,ptmp=stmp;}
	~IO(){fwrite(sw,1,pw-sw,stdout);}
	operator int(){
		int x=0;
		while(*pr<'0')++pr;
		while(*pr>='0')x=(x<<3)+(x<<1)+*pr++-'0';
		return x;
	}
	void operator = (ll x){
		if(!x)*pw++='0';
		ll y;
		while(x)y=x/10,*++ptmp=x-(y<<3)-(y<<1)+'0',x=y;
		while(ptmp!=stmp)*pw++=*ptmp--;
		*pw++='
';
	}
}io;
int n,m,q;
struct node{
	ll axy,ax,ay,a;
	void operator += (const node &b){axy+=b.axy,ax+=b.ax,ay+=b.ay,a+=b.a;}
};
struct edge{ll v;node w;edge *nxt;};
struct HASH{
	edge *hd[Mod],*cnt,e[15000000];
	HASH(){cnt=e;}
	node &adde(int u,ll v){*++cnt=(edge){v,(node){0,0,0,0},hd[u]},hd[u]=cnt;return cnt->w;}
	node &operator () (int x,int y,bool add){
		ll v=ll(x-1)*m+y;int u=(1000000007u*x-998244353*y)%Mod;
		for(edge *i=hd[u];i;i=i->nxt)if(i->v==v)return i->w;
		return add?adde(u,v):e->w;//e->w==0 which is only used to read
	}
};
struct BIT{
	HASH Hash;
	void add3(int x,int y,node a){
		for(int i=x;i<=n;i+=i&-i)
			for(int j=y;j<=m;j+=j&-j)
				Hash(i,j,1)+=a;
	}
	void add2(int x,int y,ll a){
		add3(x,y,(node){a,a*(1-y),a*(1-x),a*(1-x)*(1-y)});
	}
	void add(int lx,int ly,int rx,int ry,int a){
		add2(lx,ly,a);
		add2(lx,ry+1,-a);
		add2(rx+1,ly,-a);
		add2(rx+1,ry+1,a);
	}
	ll query2(int x,int y){
		node a=(node){0,0,0,0};
		for(int i=x;i;i^=i&-i)
			for(int j=y;j;j^=j&-j)
				a+=Hash(i,j,0);
		return a.axy*x*y+a.ax*x+a.ay*y+a.a;
	}
	ll query(int lx,int ly,int rx,int ry){
		return
		+query2(rx,ry)
		-query2(lx-1,ry)
		-query2(rx,ly-1)
		+query2(lx-1,ly-1);
	}
}Bit;
struct edge2{int v,nxt;}e[N];
struct node2{int typ,lx,ly,rx,ry,fa;ll a;}p[N];
int hd[N],cnt;
void adde(int u,int v){e[++cnt]=(edge2){v,hd[u]},hd[u]=cnt;}
void dfs(int u){
	node2 &a=p[u];
	if(a.typ==1)Bit.add(a.lx,a.ly,a.rx,a.ry,a.a);
	if(a.typ==0)a.a=Bit.query(a.lx,a.ly,a.rx,a.ry);
	for(int i=hd[u];i;i=e[i].nxt)dfs(e[i].v);
	if(a.typ==1)Bit.add(a.lx,a.ly,a.rx,a.ry,-a.a);
}
int main()
{
	n=io,m=io,q=io;
	for(int i=1,j=0,k=q+1,typ;i<=q;i++){
		typ=io;
		if(typ==0)p[--k]=(node2){typ,io,io,io,io,j,0};
		else if(typ==1)p[++j]=(node2){typ,io,io,io,io,j-1,io};
		else p[++j]=(node2){typ,0,0,0,0,j-1-io,0};	
	}
	for(int i=1;i<=q;i++)adde(p[i].fa,i);
	p[0].typ=2;
	dfs(0);
	for(int i=q;!p[i].typ;i--)io=p[i].a;
	return 0;
}

  

原文地址:https://www.cnblogs.com/Paul-Guderian/p/8901041.html