没有补题。。倒是又想到了1002的二分做法,比原来好写了不少,也快了不少。
#include
using namespace std;
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
const int maxn=3e5+5;
int n,a[maxn],dp[maxn],pre[2][maxn],nex[2][maxn];
int LIS[maxn];
struct node{
int val,pos;
node(int _v=0,int _p=0):val(_v),pos(_p){}
};
vector v[maxn];//第一维下标为LIS长度
int main(){
// freopen("in.txt","r",stdin);
while(~scanf("%d",&n)){
For(i,1,n) scanf("%d",&a[i]);
int len=1,p,l,r,mid,sz,now;//第一遍LIS
dp[len]=a[1],pre[0][1]=pre[1][1]=-1,LIS[1]=1;
v[1].p_b(node(a[1],1));
For(i,2,n){
p=lower_bound(dp+1,dp+1+len,a[i])-dp;
if(p>len) len++;
dp[p]=a[i];
v[p].p_b(node(a[i],i));
LIS[i]=p;
if(p==1) pre[0][i]=pre[1][i]=-1;
else{
sz=v[p-1].size();
pre[1][i]=v[p-1][sz-1].pos;
l=0,r=sz-1;
while(l<=r){
mid=(l+r)>>1;
if(v[p-1][mid].val>=a[i]) l=mid+1;
else now=v[p-1][mid].pos,r=mid-1;
}
pre[0][i]=now;
}
}
For(i,1,len) v[i].clear();
len=1;//第二遍LIS
dp[len]=a[n],nex[0][n]=nex[1][n]=-1;
v[1].p_b(node(a[n],n));
for(int i=n-1;i>=1;i--){
p=lower_bound(dp+1,dp+1+len,a[i])-dp;
if(p>len) len++;
dp[p]=a[i];
v[p].p_b(node(a[i],i));
LIS[i]+=p-1;
if(p==1) nex[0][i]=nex[1][i]=-1;
else{
sz=v[p-1].size();
nex[0][i]=v[p-1][sz-1].pos;
l=0,r=sz-1;
while(l<=r){
mid=(l+r)>>1;
if(v[p-1][mid].val>=a[i]) l=mid+1;
else now=v[p-1][mid].pos,r=mid-1;
}
nex[1][i]=now;
}
}
For(i,1,len) v[i].clear();
int maxx=0,maxp[2];//得到最左和最右的折点位置
For(i,1,n){
if(LIS[i]>maxx) maxx=LIS[i],maxp[0]=maxp[1]=i;
else if(LIS[i]==maxx) maxp[1]=i;
}
for(int k=0;k<2;k++){//统计答案
int i=maxp[k],j=nex[k][maxp[k]],cnt=0;
while(i!=-1){
cnt++;
a[cnt]=i;
i=pre[k][i];
}
while(j!=-1){
cnt++;
a[cnt]=j;
j=nex[k][j];
}
sort(a+1,a+cnt+1);
For(i,1,cnt) cout<
题意:求最长的先升后降子序列中下标字典序最小的和下标字典序最大的。
要求最长的先升后降子序列,可以先正反各求一遍最长上升子序列,枚举每一个点作为转折点,找出最大的即可。
求最长上升子序列有多种方法,这里我采用的是线段树。
对于数列A,每个数的初始位置为id,用结构体存下,将之按值从小到大排序。线段树维护以每个位置结尾的最长上升子序列长度,初始为0。遍历排序后的结构体数组,通过线段树找出【1,id-1】的最大值,则该位置的LIS长度为 这个最大值+1。(因为可能有多个相等的值,此处有两种处理方法,一种是排序的时候,当值相等的时候,id大的排在前面;另一种是先查询完所有相等的值,再插入。)
这样已经可以得到最长的先升后降子序列长度以及所有的转折点。
至于求字典序最大和最小的序列,可以直接贪心,若某个点在答案序列中,那么对于字典序小一定从越前面的转移过来越好,对于字典序大的一定从越后面转移过来越好。
因此在求最长上升子序列的过程中,我们除了要知道最大值之外,还要知道处于最左端/最右端的最大值的位置,这个同样可以通过线段树查询。
我的线段树写的很挫。。没有整理过很好的模板,都是直接YY,想到咋写就咋写,查询位置的那部分不知道能不能写得更优,感觉自己写的是个剪枝的log^2。。。最后2792ms,跑的倒数第4快。。。。。
AC代码:
#include
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 numb.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=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
UPD:
问了一下大大怎么用线段树快速查询已知值的位置,大大:“不用线段树,你用个map不就行了。”
所以求出最大值后,求最左端和最右端的位置我改成了用set来做,好写了不少。。。
#include
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 numb.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 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
待wtw补题解。
签到题。
因为每次只能确定一位,因此需要每一位都询问一次,不同的询问方案数其实就是位数的排列。
因为模数为1e6+3,对于n<1e6+3,可以预处理线性求解;当n>=1e6+3,答案为0。
AC代码:
#include
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) puts("0");
else cout<
次签到题。
考虑对于一个有序数列,可能形成的最大周长的三角形的取值一定是相邻的三个数,而能构造的一个三角形都不能形成的最坏情况形如斐波那契数列,即项数很小。
对于本题,我们知道主席树可以求区间第k大。对于每个区间,先求第一大、第二大、第三大,如果能组成三角形,即为答案,否则求第四大。。。至多只需要求到第44大左右。
因此时间复杂度为。
AC代码:
#include
#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<b[tmp1]){
flag=1;
cout<
对于区间覆盖问题我做的题很少,想了挺久的假做法,完全没往这方面想。
几何旋律一下就秒了,qko想做法,wang9897实现,Orz。
具体解法:
线性扫过整个数列,通过线段树维护,当前位置作为右端点时,哪些区间不能作为左端点,不能作为左端点的区间在线段树中就是一条覆盖的线段。
设当前位置为i,值为x,用名为pos的vector存下每个值的所有位置。
当[1,i]中x的个数小于k时,则对之不能作为左端点的区间是[1,i];
否则,不能作为左端点的区间是[pos[x][pos[x].size()-k]+1,i]。
同时,插入线段的时候,还需要删除前一个x的线段。即对于每个相同的值,在线段树中只需要维护一条线段,
最后找到没有被任意一条线段覆盖的最左端的点即可。
AC代码:
#include
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 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<
总结:
本场我很快的找到了签到题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做法时并没有构造反例。