• 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;
    }
    

      

  • 相关阅读:
    Coursera Algorithms week3 快速排序 练习测验: Nuts and bolts
    快速排序及三向切分快排——java实现
    自顶向下(递归)的归并排序和自底向上(循环)的归并排序——java实现
    希尔shell排序——java实现
    插入排序——java实现
    选择排序——java实现
    Coursera Algorithms week3 归并排序 练习测验: Shuffling a linked list
    单向链表的归并排序——java实现
    Coursera Algorithms week3 归并排序 练习测验: Counting inversions
    Coursera Algorithms week2 栈和队列 练习测验: Stack with max
  • 原文地址:https://www.cnblogs.com/Paul-Guderian/p/8901041.html
Copyright © 2020-2023  润新知