20190724杭電多校第二場

 

1001 Another Chess Problem(待補)

 

1002 Beauty Of Unimodal Sequence(補題 By jlz)

題意:求最長的先升後降子序列中下標字典序最小的和下標字典序最大的。

要求最長的先升後降子序列,可以先正反各求一遍最長上升子序列,枚舉每一個點作爲轉折點,找出最大的即可。

求最長上升子序列有多種方法,這裏我採用的是線段樹。

對於數列A,每個數的初始位置爲id,用結構體存下,將之按值從小到大排序。線段樹維護以每個位置結尾的最長上升子序列長度,初始爲0。遍歷排序後的結構體數組,通過線段樹找出【1,id-1】的最大值,則該位置的LIS長度爲 這個最大值+1。(因爲可能有多個相等的值,此處有兩種處理方法,一種是排序的時候,當值相等的時候,id大的排在前面;另一種是先查詢完所有相等的值,再插入。)

這樣已經可以得到最長的先升後降子序列長度以及所有的轉折點。

至於求字典序最大和最小的序列,可以直接貪心,若某個點在答案序列中,那麼對於字典序小一定從越前面的轉移過來越好,對於字典序大的一定從越後面轉移過來越好。

因此在求最長上升子序列的過程中,我們除了要知道最大值之外,還要知道處於最左端/最右端的最大值的位置,這個同樣可以通過線段樹查詢。

我的線段樹寫的很挫。。沒有整理過很好的模板,都是直接YY,想到咋寫就咋寫,查詢位置的那部分不知道能不能寫得更優,感覺自己寫的是個剪枝的log^2。。。最後2792ms,跑的倒數第4快。。。。。

AC代碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=3e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int n,a[maxn];
struct node{
	int num,id;
	bool operator<(const node &b)const{
		return num<b.num||num==b.num&&id>b.id;
	}
}b[maxn];
struct Qnode{
	int l,r,s;
}Q[maxn<<2];
void build(int rt,int l,int r){
	Q[rt].l=l,Q[rt].r=r,Q[rt].s=0;
	if(Q[rt].l==Q[rt].r) return;
	int mid=(Q[rt].l+Q[rt].r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r); 
}
inline void pushup(int rt){
	Q[rt].s=max(Q[ls].s,Q[rs].s);
} 
void insert(int rt,int p,int x){
	if(Q[rt].l==Q[rt].r){
		Q[rt].s=x;return;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(p<=mid) insert(ls,p,x);
	else insert(rs,p,x);
	pushup(rt);
}
int dp[4][maxn],LIS[2][maxn];
int query(int rt,int l,int r){//求區間最大值 
	if(l>r) return 0;
	if(Q[rt].r==r&&Q[rt].l==l||Q[rt].l==Q[rt].r){
		return Q[rt].s;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid) return query(ls,l,r);
	else return max(query(ls,l,mid),query(rs,mid+1,r));
}
int query2(int rt,int x){//left
	if(Q[rt].l==Q[rt].r){
		return Q[rt].l;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(Q[ls].s>=x) return query2(ls,x);
	else return query2(rs,x);
}
int query1(int rt,int r,int x){//right
	if(Q[rt].l==Q[rt].r){
		return Q[rt].l;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid||Q[rs].s<x) return query1(ls,r,x);
	else if(r>=Q[rt].r&&Q[rs].s>=x) return query1(rs,r,x);
	else if(query(rs,mid+1,r)>=x) return query1(rs,r,x);
	else return query1(ls,r,x);
}
int ans[2][maxn],cnt[2];
int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i].num=a[i],b[i].id=i;
		sort(b+1,b+1+n);
		build(1,1,n);
		for(int i=1;i<=n;i++){
			LIS[0][b[i].id]=query(1,1,b[i].id-1)+1;
			if(LIS[0][b[i].id]==1){
				dp[0][b[i].id]=dp[1][b[i].id]=0;
			}
			else{
				dp[0][b[i].id]=query2(1,LIS[0][b[i].id]-1);
				dp[1][b[i].id]=query1(1,b[i].id-1,LIS[0][b[i].id]-1);
			}
			insert(1,b[i].id,LIS[0][b[i].id]);
		}
		for(int i=1;i<=n;i++) b[i].id=n-b[i].id+1;
		sort(b+1,b+1+n);
		build(1,1,n);
		int p;
		for(int i=1;i<=n;i++){
			p=n-b[i].id+1;
			LIS[1][p]=query(1,1,b[i].id-1)+1;
			if(LIS[1][p]==1) dp[2][p]=dp[3][p]=0;
			else{
				dp[3][p]=query2(1,LIS[1][p]-1);
				dp[2][p]=query1(1,b[i].id-1,LIS[1][p]-1);
			}
			dp[2][p]=n-dp[2][p]+1;
			dp[3][p]=n-dp[3][p]+1;
			insert(1,b[i].id,LIS[1][p]);
		}
		int pre[2],nex[2],maxx=0;
		for(int i=1;i<=n;i++){
			if(LIS[0][i]+LIS[1][i]-1>maxx){
				maxx=LIS[0][i]+LIS[1][i]-1;
				pre[0]=pre[1]=nex[0]=nex[1]=i;
			}
			else if(LIS[0][i]+LIS[1][i]-1==maxx){
				pre[1]=nex[1]=i;
			}
		}
		for(int i=0;i<2;i++){
			cnt[i]=0;;
			while(pre[i]!=0){
				ans[i][cnt[i]++]=pre[i];
				pre[i]=dp[i][pre[i]];
			}
			nex[i]=dp[i+2][nex[i]];
			while(nex[i]!=n+1){
				ans[i][cnt[i]++]=nex[i];
				nex[i]=dp[i+2][nex[i]];
			}
			sort(ans[i],ans[i]+cnt[i]);
			for(int j=0;j<cnt[i];j++){
				cout<<ans[i][j];
				if(j!=cnt[i]-1) cout<<" ";
				else cout<<"\n";
			}
		}
	}
    return 0;
}

 

UPD:

問了一下大大怎麼用線段樹快速查詢已知值的位置,大大:“不用線段樹,你用個map不就行了。”

所以求出最大值後,求最左端和最右端的位置我改成了用set來做,好寫了不少。。。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=3e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int n,a[maxn];
struct node{
	int num,id;
	bool operator<(const node &b)const{
		return num<b.num||num==b.num&&id>b.id;
	}
}b[maxn];
struct Qnode{
	int l,r,s;
}Q[maxn<<2];
void build(int rt,int l,int r){
	Q[rt].l=l,Q[rt].r=r,Q[rt].s=0;
	if(Q[rt].l==Q[rt].r) return;
	int mid=(Q[rt].l+Q[rt].r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r); 
}
inline void pushup(int rt){
	Q[rt].s=max(Q[ls].s,Q[rs].s);
} 
void insert(int rt,int p,int x){
	if(Q[rt].l==Q[rt].r){
		Q[rt].s=x;return;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(p<=mid) insert(ls,p,x);
	else insert(rs,p,x);
	pushup(rt);
}
int dp[4][maxn],LIS[2][maxn];
int query(int rt,int l,int r){//求區間最大值 
	if(l>r) return 0;
	if(Q[rt].r==r&&Q[rt].l==l||Q[rt].l==Q[rt].r){
		return Q[rt].s;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid) return query(ls,l,r);
	else return max(query(ls,l,mid),query(rs,mid+1,r));
}
set<int> st[maxn];//維護LIS長度的位置 
int ans[2][maxn],cnt[2];
int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i].num=a[i],b[i].id=i,st[i].clear();
		sort(b+1,b+1+n);
		build(1,1,n);
		for(int i=1;i<=n;i++){
			LIS[0][b[i].id]=query(1,1,b[i].id-1)+1;
			if(LIS[0][b[i].id]==1){
				dp[0][b[i].id]=dp[1][b[i].id]=0;
			}
			else{
				dp[0][b[i].id]= *(st[LIS[0][b[i].id]-1].begin());
				dp[1][b[i].id]= *(--st[LIS[0][b[i].id]-1].lower_bound(b[i].id));
			}
			insert(1,b[i].id,LIS[0][b[i].id]);
			st[LIS[0][b[i].id]].insert(b[i].id);
		}
		for(int i=1;i<=n;i++) b[i].id=n-b[i].id+1,st[i].clear();
		sort(b+1,b+1+n);
		build(1,1,n);
		int p;
		for(int i=1;i<=n;i++){
			p=n-b[i].id+1;
			LIS[1][p]=query(1,1,b[i].id-1)+1;
			if(LIS[1][p]==1) dp[2][p]=dp[3][p]=0;
			else{
				dp[3][p]=*(st[LIS[1][p]-1].begin());
				dp[2][p]=*(--st[LIS[1][p]-1].lower_bound(b[i].id));
			}
			dp[2][p]=n-dp[2][p]+1;
			dp[3][p]=n-dp[3][p]+1;
			insert(1,b[i].id,LIS[1][p]);
			st[LIS[1][p]].insert(b[i].id);
		}
		int pre[2],nex[2],maxx=0;
		for(int i=1;i<=n;i++){
			if(LIS[0][i]+LIS[1][i]-1>maxx){
				maxx=LIS[0][i]+LIS[1][i]-1;
				pre[0]=pre[1]=nex[0]=nex[1]=i;
			}
			else if(LIS[0][i]+LIS[1][i]-1==maxx){
				pre[1]=nex[1]=i;
			}
		}
		for(int i=0;i<2;i++){
			cnt[i]=0;;
			while(pre[i]!=0){
				ans[i][cnt[i]++]=pre[i];
				pre[i]=dp[i][pre[i]];
			}
			nex[i]=dp[i+2][nex[i]];
			while(nex[i]!=n+1){
				ans[i][cnt[i]++]=nex[i];
				nex[i]=dp[i+2][nex[i]];
			}
			sort(ans[i],ans[i]+cnt[i]);
			for(int j=0;j<cnt[i];j++){
				cout<<ans[i][j];
				if(j!=cnt[i]-1) cout<<" ";
				else cout<<"\n";
			}
		}
	}
    return 0;
}

1003 Coefficient (待補)

 

1004 Double Tree (待補)

 

1005 Everything Is Generated In Equal Probability (Solved By wtw)

待wtw補題解。

 

1006 Fantastic Magic Cube (待補)

 

1007 Game (待補)

 

1008 Harmonious Army (待補)

 

1009 I Love Palindrome String (待補)

 

1010 Just Skip The Problem (Solved By jlz)

簽到題。

因爲每次只能確定一位,因此需要每一位都詢問一次,不同的詢問方案數其實就是位數的排列。

因爲模數爲1e6+3,對於n<1e6+3,可以預處理線性求解;當n>=1e6+3,答案爲0。

AC代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const int mod=1e6+3;
int f[maxn],n;
int main(){
    f[0]=1;
    for(int i=1;i<mod;i++) f[i]=(f[i-1]*1ll*i)%mod;
    while(scanf("%d",&n)!=EOF){
        if(n>=mod) puts("0");
        else cout<<f[n]<<"\n";
    }
    return 0;
}

1011 Keen On Everything But Triangle (Solved By cys/wtw)

次簽到題。

考慮對於一個有序數列,可能形成的最大周長的三角形的取值一定是相鄰的三個數,而能構造的一個三角形都不能形成的最壞情況形如斐波那契數列,即項數很小。

對於本題,我們知道主席樹可以求區間第k大。對於每個區間,先求第一大、第二大、第三大,如果能組成三角形,即爲答案,否則求第四大。。。至多只需要求到第44大左右。

因此時間複雜度爲O(m*log_{2}n*f)(f<=50)

AC代碼:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define _mp make_pair
#define db double
#define eps 1e-9
#define inf 1e9
using namespace std;
const int maxn=1e5+7;
const int mod=1e9+7;
inline ll read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct node
{
    int l,r,sum;
}no[maxn*40];
int root[maxn];
int cnt;
int n,q;
void build(int &x,int l,int r)
{
    x=++cnt;
    no[x].sum=0;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(no[x].l,l,mid);
    build(no[x].r,mid+1,r);
}
void insertt(int &x,int pre,int l,int r,int pl)
{
    x=++cnt;
    no[x]=no[pre];
    no[x].sum++;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(pl<=mid)insertt(no[x].l,no[pre].l,l,mid,pl);
    else insertt(no[x].r,no[pre].r,mid+1,r,pl);
}
int query(int x,int y,int l,int r,int tt)
{
    if(l==r)return l;
    int mid=(l+r)>>1;
    int tmp=no[no[y].l].sum-no[no[x].l].sum;
    if(tt<=tmp)return query(no[x].l,no[y].l,l,mid,tt);
    if(tt>tmp)return query(no[x].r,no[y].r,mid+1,r,tt-tmp);
}
ll a[maxn],b[maxn],kv[maxn];
int main()
{
    while(cin>>n>>q){
        for(int i=1;i<=n;i++){
            a[i]=read();b[i]=a[i];
        }
        cnt=0;
        sort(b+1,b+1+n);
        int tt=unique(b+1,b+1+n)-b;
       // cout<<tt<<"\n";
        for(int i=1;i<=n;i++){
            kv[i]=lower_bound(b+1,b+tt,a[i])-b;
            //cout<<kv[i]<<"\n";
        }
        build(root[0],1,tt);
        for(int i=1;i<=n;i++){
            insertt(root[i],root[i-1],1,tt,kv[i]);
        }
        int l,r;
        for(int i=1;i<=q;i++){
            l=read();r=read();
            int flag=0;
            if(r-l+1<3){cout<<-1<<"\n";continue;}
            ll tmp1=query(root[l-1],root[r],1,tt,r-l+1);
            ll tmp2=query(root[l-1],root[r],1,tt,r-l);
            for(int j=3;j<=r-l+1;j++){
                ll tmp3=query(root[l-1],root[r],1,tt,r-l+2-j);
                if(b[tmp2]+b[tmp3]>b[tmp1]){
                    flag=1;
                    cout<<b[tmp1]+b[tmp2]+b[tmp3]<<"\n";
                    break;
                }
                else{
                    tmp1=tmp2;tmp2=tmp3;
                }
            }
            if(!flag)cout<<-1<<"\n";
        }
    }
}

1012 Longest Subarray (補題 By jlz)

對於區間覆蓋問題我做的題很少,想了挺久的假做法,完全沒往這方面想。

幾何旋律一下就秒了,qko想做法,wang9897實現,Orz。

具體解法:

線性掃過整個數列,通過線段樹維護,當前位置作爲右端點時,哪些區間不能作爲左端點,不能作爲左端點的區間在線段樹中就是一條覆蓋的線段。

設當前位置爲i,值爲x,用名爲pos的vector存下每個值的所有位置。

當[1,i]中x的個數小於k時,則對之不能作爲左端點的區間是[1,i];

否則,不能作爲左端點的區間是[pos[x][pos[x].size()-k]+1,i]。

同時,插入線段的時候,還需要刪除前一個x的線段。即對於每個相同的值,在線段樹中只需要維護一條線段,

最後找到沒有被任意一條線段覆蓋的最左端的點即可。

AC代碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=1e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int n,c,k,sz,x;
struct node{
	int l,r,sum,s;
}Q[maxn<<2];
vector<int> pos[maxn];
inline void pushup(int rt){
	if(!Q[rt].s&&Q[rt].l!=Q[rt].r) Q[rt].sum=Q[ls].sum+Q[rs].sum;
	else if(!Q[rt].s&&Q[rt].l==Q[rt].r) Q[rt].sum=1;
	else Q[rt].sum=0;
}
void build(int rt,int l,int r){
	Q[rt].l=l,Q[rt].r=r,Q[rt].s=0;
	if(l==r){
		Q[rt].sum=1;
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(rt);
}
void update(int rt,int l,int r,int op){
	if(Q[rt].l==l&&Q[rt].r==r){
		if(op==1) Q[rt].s++;
		else Q[rt].s--;
		pushup(rt);
		return ;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid) update(ls,l,r,op);
	else if(l>mid) update(rs,l,r,op);
	else update(ls,l,mid,op),update(rs,mid+1,r,op);
	pushup(rt);
}
int query(int rt,int r){
	if(Q[rt].sum==0) return r;
	if(Q[rt].l==Q[rt].r){
		return Q[rt].l;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid||Q[ls].sum>0) return query(ls,r);
	else return query(rs,r);
}
int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d %d %d",&n,&c,&k)){
		if(k==1){
			for(int i=1;i<=n;i++) scanf("%d",&x);
			cout<<n<<"\n";
			continue;
		}
		build(1,1,n);
		for(int i=1;i<=c;i++) pos[i].clear();
		int ans=0,tmp,l,l1,r,r1;
		for(int i=1;i<=n;i++){
			scanf("%d",&x);
			pos[x].p_b(i);
			sz=pos[x].size();
			if(sz==1) update(1,1,i,1);
			else if(sz<k) l=1,r=pos[x][sz-2],l1=1,r1=i;
			else if(sz==k) l=1,r=pos[x][sz-2],l1=pos[x][sz-k]+1,r1=i;
			else l=pos[x][sz-k-1]+1,r=pos[x][sz-2],l1=pos[x][sz-k]+1,r1=i;
			if(sz!=1){
				update(1,l,r,-1);
				update(1,l1,r1,1);
			}
			tmp=query(1,i);
			if(ans<=i-tmp&&i!=tmp) ans=i-tmp+1;
		}
		cout<<ans<<"\n";
	}
    return 0;
}

 

總結:

本場我很快的找到了簽到題1010,過題時排在榜單第15。。然而通常如果前期過於舒服,中後期我就會成爲演員。

一段時間後cys說1011可做,主席樹暴力找第一大、第二大...我說這顯然是m*n*logn,wtw也說這複雜度不對。cys小聲抗議了幾句,被我無視了QAQ。

然後cys看1002,wtw搞1005,同時和我想1011。一段時間後,我提出了O(n*sqrt(n)*log(n))的做法,莫隊+set,也沒算複雜度,然後成功演了近三個小時。。。(期間隊友要求我去搞別的題依然被我無視QAQ)

wtw搞了挺久1005,過了,cys一直在搞1002,他想到了解法,但是不會線段樹求LIS,現學一段時間之後放棄。

最後一小時,確定莫隊過不了1011,又一起討論了做法,想了想是不是構造不出m*n*logn的情況,然後發現最壞情況符合斐波那契數列。。。那麼複雜度實際上就是m*logn*(斐波那契項數),也可以近似爲log級別。

然後wtw採用cys最初的想法,寫了20+分鐘過了。

最後時間剩下半小時,我想了個1012的假做法,也沒寫完。。。

這場比較大的問題在我:沒有聽從隊友的建議想其他題;沒有給予自閉1002的cys幫助;叉cys做法時並沒有構造反例。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章