动态规划:决策单调性优化 dp
AstralBrahma · · 算法·理论
1. 引入
决策单调性类似于斜率优化,说人话就是,在 dp 的时候,对着
这个东西,我们不太好直接讲,因此,我们用几个题目来引入。
我们以 P3515 [POI 2011] Lightning Conductor 这道题来作为引入。
提取一下题目的关键信息,注意到说,对于一栋固定的建筑
考虑转换一下视角,我们可以把
注意到从左往右和从右往左是镜像的,因此只需要正着处理一次,倒着处理一次即可。现在就是考虑如何处理的问题。
画图可以感受到,对于两个函数
考虑简单的证明一下,做差即可,有
考虑到说这个的
那么就不难想到说,对于一个在当前是最优的函数
具体的,我们考虑去枚举一个函数
考虑优化,貌似可以用单调队列进行优化。
具体的,我们用一个
那么我们在外层枚举
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int MAXN=5e5+10;
ll n,h[MAXN],tmp[MAXN],q[MAXN],pos[MAXN];
ll L[MAXN],R[MAXN];
namespace yixing{
inline bool better(int j,int k,int x){
ld x1=(ld)h[j]+sqrtl(x-j);
ld x2=(ld)h[k]+sqrtl(x-k);
return x2>=x1;
}
inline int cross(int j,int k){
if (!better(j,k,n))return n+1;
int l=k,r=n,res=l;
while (l<=r){
int mid=(l+r)>>1;
if (better(j,k,mid))r=mid-1,res=mid;
else l=mid+1;
}
return res;
}
inline void work(ll *ans){
int head=1,tail=0;
for (int i=1;i<=n;i++){
while (head<=tail){
int p=cross(q[tail],i);
if (p<=pos[tail])tail--;
else break;
}
if (head>tail){
head=tail=1;
q[1]=pos[1]=i;
}else {
int p=cross(q[tail],i);
if (p<=n)q[++tail]=i,pos[tail]=p;
}
while (head<tail&&pos[head+1]<=i)head++;
int j=q[head];
ans[i]=h[j]+ceil(sqrt(i-j));
}
}
inline void Main(){
cin>>n;
for (int i=1;i<=n;i++)cin>>h[i];
work(L);
reverse(h+1,h+1+n);
work(tmp);
for (int i=1;i<=n;i++)R[i]=tmp[n-i+1];
reverse(h+1,h+1+n);
for (int i=1;i<=n;i++)cout<<max(L[i],R[i])-h[i]<<"\n";
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
这种写法固然可以,甚至于说可以比较好的和后面的 wqs 二分结合,但是分治的写法仍然比较重要。
考虑
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int MAXN=5e5+10;
const ll INF=4e18;
int n,h[MAXN];
ll L[MAXN],R[MAXN];
namespace yixing{
inline void solve(int l,int r,int L,int R,ll *ans){
if (l>r)return;
int mid=(l+r)>>1,pos=-1;
ld mx=-INF;
for (int j=L;j<=min(mid,R);j++){
ld val=(ld)h[j]+sqrtl(mid-j);
if (val>mx)mx=(ld)h[j]+sqrtl(mid-j),pos=j;
}
ans[mid]=h[pos]+ceil(sqrtl((ld)mid-pos));
solve(l,mid-1,L,pos,ans);
solve(mid+1,r,pos,R,ans);
}
inline void Main(){
cin>>n;
for (int i=1;i<=n;i++)cin>>h[i];
solve(1,n,1,n,L);
reverse(h+1,h+1+n);
solve(1,n,1,n,R);
reverse(h+1,h+1+n),reverse(R+1,R+1+n);
for (int i=1;i<=n;i++)cout<<max(L[i],R[i])-h[i]<<"\n";
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
2. 四边形不等式
然后考虑这道题 P4767 [IOI 2000] 邮局 加强版。
还是先考虑暴力嘛,不难想到说,我们可以去确定那些村庄要建立邮局,具体的,我们令
那么自然有
比较显然,对于区间
:::info[Code]
for (int j=1;j<=n;j++){
int mid=j;
for (int i=j+1;i<=n;i++){
while (mid+1<i&&2*a[mid+1]<=a[j]+a[i])mid++;
ll L=(s[mid]-s[j])-1ll*(mid-j)*a[j];
ll R=1ll*(i-mid-1)*a[i]-(s[i-1]-s[mid]);
w[j][i]=L+R;
}
}
:::
然后考虑其他的部分。因为此时是和
:::success[知识点:四边形不等式]{open}
此处介绍一个东西叫做四边形不等式,具体的,我们取
也就是说,我们可以从
:::
那么类似的,如果说我们固定
那么现在的问题就是考虑如何证明。同样的,取
考虑如何证明。貌似可以画图然后分类讨论来做。思考一下
考虑一个具体的村庄
-
首先是
t\in (q,i) ,然后无论左侧是p 还是q ,我们都会去i ,这种对答案的贡献就是0 ,因为没有变化。 -
然后就是
t\in (q,i) ,当当前的区间是(q,i) 的时候,我们去q ,当当前区间是(p,i) 的时候,我们回去i ,那么此时对答案的贡献就是(a_i-a_t)-(a_t-a_q)=a_i-2a_t+a_q ,很显然,在固定t 的时候,是随着i 的增加而增加的。 -
最后就是
t\in (q,i) ,然后两次都选择左侧的邮局,所以说贡献就是a_t-a_p-(a_t-a_q)=a_q-a_p 是个定值。
不难发现对于这种情况,
还有一种情况就是
那么就可以说明
但是我们现在是只做完了和
那么我们现在就是可以预见性的,在
那么就可以考虑一种分治的做法。具体的,我们知道一个区间
整体的大小就是
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e3+10;
const ll INF=4e18;
int n,P,a[MAXN],s[MAXN],w[MAXN][MAXN];
ll f[MAXN],g[MAXN];
namespace yixing{
inline void solve(int l,int r,int L,int R){
if (l>r)return;
int mid=(l+r)>>1,pos=-1;
g[mid]=INF;
for (int j=L;j<=min(mid-1,R);j++){
ll val=f[j]+w[j][mid];
if (val<g[mid])g[mid]=val,pos=j;
}
solve(l,mid-1,L,pos);
solve(mid+1,r,pos,R);
}
inline void Main(){
cin>>n>>P;
for (int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
for (int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
for (int j=1;j<=n;j++){
int mid=j;
for (int i=j+1;i<=n;i++){
while (mid+1<i&&2*a[mid+1]<=a[j]+a[i])mid++;
ll L=(s[mid]-s[j])-1ll*(mid-j)*a[j];
ll R=1ll*(i-mid-1)*a[i]-(s[i-1]-s[mid]);
w[j][i]=L+R;
}
}
for (int i=1;i<=n;i++)f[i]=1ll*(i-1)*a[i]-s[i-1];
for (int k=2;k<=P;k++){
for (int i=1;i<=n;i++)g[i]=INF;
solve(k,n,k-1,n-1);
for (int i=1;i<=n;i++)f[i]=g[i];
}
ll ans=INF;
for (int i=P;i<=n;i++){
ll R=(s[n]-s[i])-1ll*(n-i)*a[i];
ans=min(ans,f[i]+R);
}
cout<<ans<<"\n";
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
3. WQS 二分
考虑能否在这个基础上更进一步呢,考虑 P6246 [IOI 2000] 邮局 加强版 加强版。
:::success[知识点:WQS 二分]{open}
考虑说上述导致时间复杂度很高的原因是什么,不难发现说是这个恰好为
貌似是可以的,我们考虑去增加一个权值
那么我们现在就可以把原本的 dp 转移式子给简化成
同时为了方便计算,我们此处将
这样显然是正确的,因为我们会去枚举
:::
有了这个关系我们现在就可以很轻易的做到
那么此时的问题就是,如何优化内层的 dp 了。还是考虑决策单调性的问题,就是随着
考虑利用函数单调性的定义,我们取
观察,我们要求的其实就是
那么
然后又因为
也就是说,内层的转移可以用决策单调性进行优化。考虑用队列来完成这个事情,具体的,我们开三个变量
然后就是考虑剔除一些劣质的答案,具体的,对于一个现在在队末的方案
然后就考虑入队列,由于此时的队末的元素是有用的,那么我们要找的就是
这样的时间复杂度就是
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+10;
const int INF=1e9+10;
int n,m,a[MAXN];
ll s[MAXN];
struct Node{ll val,cnt;}f[MAXN];
namespace yixing{
struct Q{int k,l,r;}q[MAXN];
inline ll w(int l,int r){
ll mid=(l+r)>>1;
return 1ll*a[mid]*(mid-l+1)-(s[mid]-s[l-1])+(s[r]-s[mid])-(r-mid)*a[mid];
}
inline bool better(int x,int y,int k){
ll X=f[x].val+w(x+1,k),Y=f[ y].val+w(y+1,k);
if (X!=Y)return X<Y;
return f[x].cnt>f[y].cnt;
}
inline int check(int C){
int head=1,tail=1;
q[1]={0,1,n};
for (int i=1;i<=n;i++){
while (head<=tail&&q[head].r<i)head++;
int j=q[head].k;
f[i].val=f[j].val+w(j+1,i)+C;
f[i].cnt=f[j].cnt+1;
while (head<=tail){
int L=max(q[tail].l,i+1);
if (better(i,q[tail].k,L))tail--;
else break;
}
if (tail<head){q[++tail]={i,i+1,n};continue;}
int old=q[tail].k;
int l=max(q[tail].l,i+1),r=n,pos=n+1;
while (l<=r){
int mid=(l+r)>>1;
if (better(i,old,mid))pos=mid,r=mid-1;
else l=mid+1;
}
if (pos<=n){
q[tail].r=pos-1;
q[++tail]={i,pos,n};
}
}
return f[n].cnt;
}
inline void Main(){
cin>>n>>m;
for (int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
for (int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
int l=0,r=INF,res=0;
while (l<=r){
int mid=(l+r)>>1;
if (check(mid)>=m)res=mid,l=mid+1;
else r=mid-1;
}
check(res);
cout<<f[n].val-res*m<<"\n";
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
4. 一些例题
考虑 P1912 [NOI2009] 诗人小 G 这道题。
分析题目,可知这道题是简单的。具体的我们令
考虑
考虑优化,注意到
继续考虑优化,考虑决策单调性。
把四边形不等式搬出来,有取
令
考虑这个事情,单独看
我们把这个式子写出来,有
我们令
考虑固定
注意到题目还要求输出整体的排版方式,考虑记录前驱,那么这里就不太方便用分治来做了,考虑单调队列的方式即可。
一个细节,string 做加法是
另外一个细节,此处不能在 __int128 或是 long double 来存储即可。
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int MAXN=1e5+10;
const ld INF=1e18;
int n,L,p;
string s[MAXN];
ld dp[MAXN],pre[MAXN],sum[MAXN],len[MAXN];
namespace Tool{
inline ld quick_pow(ld a,ll b){
ld res=1;
while (b){
if (b&1)res=res*a;
a=a*a;
b>>=1;
}
return res;
}
}
namespace yixing{
struct Node{int k,l,r;}q[MAXN];
inline ld w(int l,int r){return Tool::quick_pow(llabs(sum[r]-sum[l-1]+(r-l)-L),p);}
inline bool better(int x,int y,int k){
ld X=dp[x]+w(x+1,k),Y=dp[y]+w(y+1,k);
return X<=Y;
}
struct sta{int l,r;};
inline void print(){
stack<sta> st;
int pos=n;
while (pos){
st.push(sta{pre[pos]+1,pos});
pos=pre[pos];
}
while (!st.empty()){
int l=st.top().l,r=st.top().r;
for (int i=l;i<=r;i++){
cout<<s[i];
if (i!=r)cout<<" ";
}
cout<<"\n";
st.pop();
}
}
inline void solve(){
int head=1,tail=0;
q[++tail]={0,1,n};
memset(dp,0,sizeof(dp)),memset(pre,0,sizeof(pre));
for (int i=1;i<=n;i++){
while (q[head].r<i)head++;
int j=q[head].k;
dp[i]=dp[j]+w(j+1,i),pre[i]=j;
while (head<=tail){
int L=max(q[tail].l,i+1);
if (better(i,q[tail].k,L))tail--;
else break;
}
if (head>tail){head=tail=1,q[1]={i,i+1,n};continue;}
int old=q[tail].k;
int l=max(q[tail].l+1,i+1),r=n,pos=n+1;
while (l<=r){
int mid=(l+r)>>1;
if (better(i,old,mid))pos=mid,r=mid-1;
else l=mid+1;
}
if (pos<=n){
q[tail].r=pos-1;
q[++tail]={i,pos,n};
}
}
if (dp[n]>INF)cout<<"Too hard to arrange\n";
else {
cout<<(ll)dp[n]<<"\n";
print();
}
}
inline void Main(){
cin>>n>>L>>p;
for (int i=1;i<=n;i++){
cin>>s[i];
len[i]=s[i].length(),sum[i]=sum[i-1]+len[i];
}
solve();
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int T;cin>>T;
while(T--){
yixing::Main();
cout<<"--------------------";
if (T!=0)cout<<"\n";
}
return 0;
}
:::
考虑 P3724 [AHOI2017/HNOI2017] 大佬 这道题。
分析一下题目,不难发现,操作
考虑我们此处的答案,貌似应该是全局的最大值,因为我们可能中间就把大佬给打死了,不一定要到
:::info[Code]{open}
inline void init_dp(){
memset(dp,0xc0,sizeof(dp));
dp[0][mc]=0;D=0;
for (int i=1;i<=n;i++){
for (int j=a[i];j<=mc;j++){
if (dp[i-1][j]<0)continue;
int x=j-a[i];
dp[i][x]=max(dp[i][x],dp[i-1][j]+1);
x=min(mc,j-a[i]+w[i]);
dp[i][x]=max(dp[i][x],dp[i-1][j]);
}
for (int j=0;j<=mc;j++)D=max(D,dp[i][j]);
}
}
:::
那么我们现在就变成了,通过操作
我们记录上述的最大值为
如果说只操作一次的话,我们要找的就是看是否有一个三元组
如果说操作两次的话,我们要找的就是两个三元组
把后面的式子移一下项,有
然后我们考虑从末尾进行枚举
那么现在的问题就是如何求三元组。考虑暴力的方式。具体的,考虑 BFS。即
由于在
BFS 完之后,处理一下去重的问题即可。
:::info[Code]
include<bits/stdc++.h>
define ll long long
define uch unsigned char
using namespace std; const int MAXN=105; const int MAXS=7e6+10; const int Mod=2e6+3; const int INF=1e9; int n,m,mc,a[MAXN],w[MAXN],c[MAXN],cnt; ll dp[MAXN][MAXN],D,maxC; namespace HASH{ struct Node{ int F,nxt; uch L,d; }s[MAXS]; int head[Mod],tot; inline int Hash(int F,int L){return (1ullF233+L)%Mod;} inline bool insert(int F,int L,int dep){ int h=Hash(F,L); for (int i=head[h];i;i=s[i].nxt)if (s[i].F==F&&s[i].L==L)return 0; s[++tot]={F,head[h],(uch)L,(uch)dep}; head[h]=tot; return 1; } inline bool cmp(const Node a1,const Node a2){ if (a1.F!=a2.F)return a1.F<a2.F; return a1.d<a2.d; } } namespace yixing{ inline void init_dp(){ memset(dp,0xc0,sizeof(dp)); dp[0][mc]=0;D=0; for (int i=1;i<=n;i++){ for (int j=a[i];j<=mc;j++){ if (dp[i-1][j]<0)continue; int x=j-a[i]; dp[i][x]=max(dp[i][x],dp[i-1][j]+1); x=min(mc,j-a[i]+w[i]); dp[i][x]=max(dp[i][x],dp[i-1][j]); } for (int j=0;j<=mc;j++)D=max(D,dp[i][j]); } } inline void bfs(){ if (D<4||maxC<=D)return; using namespace HASH; insert(1,1,1); for (int u=1;u<=tot;u++){ int F=s[u].F,L=s[u].L,dep=s[u].d; if (dep>=D-1)continue; if (1llFL>maxC)continue; if (1llF(L+1)<=maxC)insert(F,L+1,dep+1); if (L>1&&1llFL<=maxC)insert(FL,L,dep+1); } sort(s+1,s+tot+1,cmp); cnt=0; for (int i=1,j;i<=tot;i=j){ j=i+1; while (j<=tot&&s[j].F==s[i].F)j++; if (s[i].F>1)s[++cnt].F=s[i].F,s[cnt].d=s[i].d+1; } } inline bool check(int c){ using namespace HASH; if (c<=D)return 1; for (int i=1;i<=cnt;i++){ int F=s[i].F,d=s[i].d; if (F>c)break; if (c-F<=D-d)return 1; } int p=1,mx=-INF; for (int i=cnt;i>=1;i--){ int F=s[i].F,d=s[i].d; while (p<=cnt&&1lls[p].F+F<=c){ mx=max(mx,s[p].F-(int)s[p].d); p++; } if (mx!=-INF&&mx+F-d>=c-D)return 1; } return 0; } inline void Main(){ cin>>n>>m>>mc; for (int i=1;i<=n;i++)cin>>a[i]; for (int i=1;i<=n;i++)cin>>w[i]; for (int i=1;i<=m;i++)cin>>c[i],maxC=max(maxC,1ll*c[i]); init_dp(); bfs(); for (int i=1;i<=m;i++)cout<<check(c[i])<<"\n"; } } int main(){ ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); yixing::Main(); return 0; }
#include<bits/stdc++.h>
#define ll long long
#define uch unsigned char
using namespace std;
const int MAXN=105;
const int MAXS=7e6+10;
const int Mod=2e6+3;
const int INF=1e9;
int n,m,mc,a[MAXN],w[MAXN],c[MAXN],cnt;
ll dp[MAXN][MAXN],D,maxC;
namespace HASH{
struct Node{
int F,nxt;
uch L,d;
}s[MAXS];
int head[Mod],tot;
inline int Hash(int F,int L){return (1ull*F*233+L)%Mod;}
inline bool insert(int F,int L,int dep){
int h=Hash(F,L);
for (int i=head[h];i;i=s[i].nxt)if (s[i].F==F&&s[i].L==L)return 0;
s[++tot]={F,head[h],(uch)L,(uch)dep};
head[h]=tot;
return 1;
}
inline bool cmp(const Node a1,const Node a2){
if (a1.F!=a2.F)return a1.F<a2.F;
return a1.d<a2.d;
}
}
namespace yixing{
inline void init_dp(){
memset(dp,0xc0,sizeof(dp));
dp[0][mc]=0;D=0;
for (int i=1;i<=n;i++){
for (int j=a[i];j<=mc;j++){
if (dp[i-1][j]<0)continue;
int x=j-a[i];
dp[i][x]=max(dp[i][x],dp[i-1][j]+1);
x=min(mc,j-a[i]+w[i]);
dp[i][x]=max(dp[i][x],dp[i-1][j]);
}
for (int j=0;j<=mc;j++)D=max(D,dp[i][j]);
}
}
inline void bfs(){
if (D<4||maxC<=D)return;
using namespace HASH;
insert(1,1,1);
for (int u=1;u<=tot;u++){
int F=s[u].F,L=s[u].L,dep=s[u].d;
if (dep>=D-1)continue;
if (1ll*F*L>maxC)continue;
if (1ll*F*(L+1)<=maxC)insert(F,L+1,dep+1);
if (L>1&&1ll*F*L<=maxC)insert(F*L,L,dep+1);
}
sort(s+1,s+tot+1,cmp);
cnt=0;
for (int i=1,j;i<=tot;i=j){
j=i+1;
while (j<=tot&&s[j].F==s[i].F)j++;
if (s[i].F>1)s[++cnt].F=s[i].F,s[cnt].d=s[i].d+1;
}
}
inline bool check(int c){
using namespace HASH;
if (c<=D)return 1;
for (int i=1;i<=cnt;i++){
int F=s[i].F,d=s[i].d;
if (F>c)break;
if (c-F<=D-d)return 1;
}
int p=1,mx=-INF;
for (int i=cnt;i>=1;i--){
int F=s[i].F,d=s[i].d;
while (p<=cnt&&1ll*s[p].F+F<=c){
mx=max(mx,s[p].F-(int)s[p].d);
p++;
}
if (mx!=-INF&&mx+F-d>=c-D)return 1;
}
return 0;
}
inline void Main(){
cin>>n>>m>>mc;
for (int i=1;i<=n;i++)cin>>a[i];
for (int i=1;i<=n;i++)cin>>w[i];
for (int i=1;i<=m;i++)cin>>c[i],maxC=max(maxC,1ll*c[i]);
init_dp();
bfs();
for (int i=1;i<=m;i++)cout<<check(c[i])<<"\n";
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
考虑 P5574 [CmdOI2019] 任务分配问题 这道题。
很容易转换成
比较显然的一个事情是,
首先一个想法就是用树状数组来存储,因为你考虑在分治的过程中,你是要一个一个进行枚举的,所以说这个过程就是可以直接用树状数组来做。
但是就是问题在于,这样的时间复杂度是
考虑一种比较经典的策略,即对于一个区间,从
注意到这种本质上来说就是莫队,而又因为,我们是在分治的时候进行的处理,所以说相邻区间之间的差距很小,同时二者的时间复杂度也不再是嵌套的,这样就可以过了。
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2.5e4+10;
const int INF=1e9+10;
int n,k,a[MAXN];
ll f[MAXN],g[MAXN];
struct BIT{
int tree[MAXN],cnt=0;
inline void update(int p,int x){
cnt+=x;
for (int i=p;i<=n;i+=(i&(-i)))tree[i]+=x;
}
inline int query(int p){
int res=0;
for (int i=p;i;i-=(i&(-i)))res+=tree[i];
return res;
}
}T;
namespace yixing{
int ql=1,qr=0;ll w=0;
inline void addL(){
w+=T.cnt-T.query(a[--ql]);
T.update(a[ql],1);
}
inline void addR(){
w+=T.query(a[++qr]-1);
T.update(a[qr],1);
}
inline void delL(){
w-=T.cnt-T.query(a[ql]);
T.update(a[ql++],-1);
}
inline void delR(){
w-=T.query(a[qr]-1);
T.update(a[qr--],-1);
}
inline void move(int l,int r){
while (ql>l)addL();
while (qr<r)addR();
while (ql<l)delL();
while (qr>r)delR();
}
inline void solve(int l,int r,int L,int R){
if (l>r)return;
int mid=(l+r)>>1,pos=-1;
g[mid]=INF;
for (int j=min(R,mid-1);j>=L;j--){
move(j+1,mid);
if (g[mid]>f[j]+w)g[mid]=f[j]+w,pos=j;
}
solve(l,mid-1,L,pos),solve(mid+1,r,pos,R);
}
inline void Main(){
cin>>n>>k;
for (int i=1;i<=n;i++)cin>>a[i];
memset(f,0x3f,sizeof(f));
f[0]=0;
for (int i=1;i<=k;i++){
memset(g,0,sizeof(g));
solve(i,n,i-1,n-1);
for (int i=0;i<=n;i++)f[i]=g[i];
}
cout<<f[n]<<"\n";
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::