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++='\n';
}
}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;
}