OI 笑传 #34

今天是 bct Day4,赛时 \(75+30+40+0=145\),rk 54。

T1 挂分原因仍未知,直接原因是没有大样例,然后是用数据结构维护的贪心,比较恶心。

赛时比较爆炸,T1 连想带调用了 3h,导致比较简单的 T2,T3,T4 没有时间去拼更多的部分分。

T1 满分+所有部分分一共有 \(220\),大失败。

T2,T3 的 \(70\) 是 1h20min 拼出来的。还好这些都拼到了。

NOIP 不要这样啊。

T1

放下题面吧:

我给你一个一行有 \(n\) 个卡牌的卡牌序列,每个卡牌上写着一个 \(1\)\(9\) 的整数,把这些整数从左到右排好就组成了一个十进制整数。 现在你有 \(k\) 次交换相邻卡牌的机会,请计算能得到的最小的正整数。

\(n\le 10^5,k\le10^{18}\)。时限 1s,空限 512Mib。

例子:input 21241127 10 out:11122247

多组测试,\(\sum n \le 10^5\)

我的做法是考虑维护一个指针指向处理到的位置,找到从这个位置开始向后 \(k\) 个位置中最小的数且是最靠左的,把它拿到最前面来。

实现难度在于如何快速维护拿到前面这个过程,赛时写了个树状数组。

但是因为未知原因有 RE 有 WA。但这样做应该是很对的,可能是维护有问题,因为很难写,这几天最难写的 T1.

而且还不给大样例/fn,谴责出题人。

75pts code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e5+5;
int c[N];
int len;
int itt;
void add(int p){
  for(;p<=len;p+=((-p)&p)){c[p]++;}
}
int qry(int p){
  int ans=0;
  for(;p>0;p-=((-p)&p))ans+=c[p];
  return ans;
}
int gt(int p){
  return itt-qry(p-1);
}
struct seg{
  int l;int r;
  int mv;
  int mid;
}t[N<<2];
string s;
int pmax=0;
void b(int p,int l,int r){
  t[p].l=l;t[p].r=r;
  pmax=max(p,pmax);
  if(l==r){
    t[p].mv=s[l]-'0';
    t[p].mid=l;
    return ;
  }
  int mid=l+r>>1;
  b(ls,l,mid);b(rs,mid+1,r);
  if(t[ls].mv<=t[rs].mv){
    t[p].mv=t[ls].mv;
    t[p].mid=t[ls].mid;
  }
  else {
    t[p].mv=t[rs].mv;
    t[p].mid=t[rs].mid;
  }
  return ;
}
void reg(int p,int pos){
  if(t[p].l==t[p].r&&t[p].l==pos){t[p].mv=10;return ;}
  int mid=t[p].l+t[p].r>>1;
  if(pos<=mid)reg(ls,pos);
  if(mid<pos) reg(rs,pos);
  if(t[ls].mv<=t[rs].mv){
    t[p].mv=t[ls].mv;
    t[p].mid=t[ls].mid;
  }
  else {
    t[p].mv=t[rs].mv;
    t[p].mid=t[rs].mid;
  }
}
seg query(int p,int l,int r){
  if(l<=t[p].l&&t[p].r<=r){
    return t[p];
  }
  int mid=t[p].l+t[p].r>>1;
  if(l<=mid&&mid<r){
    seg zuo=query(ls,l,r);
    seg you=query(rs,l,r);
    seg res;
    if(zuo.mv<=you.mv){
      res.mv=zuo.mv;
      res.mid=zuo.mid;
    }
    else {
      res.mv=you.mv;
      res.mid=you.mid;
    }
    return res;
  }
  if(l<=mid){
    return query(ls,l,r);
  }
  if(mid<r){
    return query(rs,l,r);
  }
}
int main(){
  
  freopen("sbnum.in","r",stdin);
  freopen("sbnum.out","w",stdout);

  int T;cin>>T;
  while(T--){
    cin>>s;
    len=s.size();
    s=' '+s;
    b(1,1,len);
    i64 k;cin>>k;
    int pin=1;
    string ans="";
    while(k&&pin<=len){
      int l=pin;
      i64 r=pin+gt(pin)+k;
      r=min(r,0ll+len);
      seg rm=query(1,l,r);
      if(rm.mv==s[pin]-'0'){
      	ans=ans+s[pin];
        pin++;
      }
      else{
        ans=ans+(char)(rm.mv+'0');
        k-=(rm.mid-pin-(qry(rm.mid)-qry(pin-1)));
        reg(1,rm.mid);
        add(rm.mid-1);
        itt++;
      }
      while(pin<=len&&query(1,pin,pin).mv==10)pin++;
    }
    while(pin<=len){
    	while(pin<=len&&query(1,pin,pin).mv==10)pin++;
    	ans=ans+s[pin];
      pin++;
		}
    cout<<ans<<'\n';
    for(int i=0;i<=len;i++){
    	c[i]=0;
		}
		itt=len=0;
    for(int i=0;i<=pmax;i++){
 			t[i].l=t[i].r=0;
			t[i].mid=t[i].mv=0;   	
		}
		pmax=0;
  }
  
  return 0;
}

标答是这样:贪心的考虑让第一位是 \(1\),做不到后再依次考虑 \(2,3,\cdots,9\)

设第一位是 \(x\),则我们应当把所有 \(x\) 位最靠左的那一位交换过来,同时忽略掉第一位考虑剩下的子问题。

每次计算交换次数可以用树状数组快速维护,即维护还没有被换到前面的位,时间复杂度 \(O(n\log n)\)

看起来和我的差不多吧,std 实现的也比较麻烦。

T2

构造题,我??

\(n\le 5\) 的构造我就写了一堆猎奇东西:

这东西还怪难调的,调了快 40min。

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
pair<int,int> pmi[10000];
vector<int> e[10000],dg[10000];
int inner[10000],ie[10000],iem[10000];
bitset<10000> vis,onuse;
bool dfs(int u,int tg){
  vis[u]=1;
  for(int v:e[u]){
    if(vis[v])continue;
    if(v==tg)return 1;
    if(dfs(v,tg)){
      dg[u].push_back(v);
      inner[v]++;
      return 1;
    }
  }
  return 0;
}
int main(){
  
  freopen("catapult.in","r",stdin);
  freopen("catapult.out","w",stdout);
  
  int n,m;cin>>n>>m;
  int vc[123];
  for(int i=1;i<=m;i++){
    int a,b;cin>>a>>b;
    pmi[i].first=a;
    pmi[i].second=b;
  }
  vector<pair<int,int> > ctr,ans;
  if(n<=5){
  	int used=0,mused=1e9;
    for(int j1=1;j1<=n;j1++)for(int j2=1;j2<=n;j2++)for(int j3=1;j3<=n;j3++)for(int j4=1;j4<=n;j4++)for(int j5=1;j5<=n;j5++){
    	vc[1]=j1;vc[2]=j2;vc[3]=j3;vc[4]=j4;vc[5]=j5;
    	used=0;
    	onuse.reset();
      for(int i=1;i<=n;i++)e[i].clear(),dg[i].clear(),ie[i]=inner[i]=0;
      for(int i=1;i<=n;i++){
        if(vc[i]!=i)
          e[i].push_back(vc[i]),used++,ie[vc[i]]++,onuse[i]=1;
      }
      bool fir=1;
      for(int i=1;i<=m;i++){
      	vis.reset();
      	if(!onuse[pmi[i].first]){
      		fir=0;break;
				}
        if(onuse[pmi[i].first]&&!dfs(pmi[i].first,pmi[i].second)){fir=0;break;}
      }
      if(!fir)continue;
      queue<int> q,qm;
      int cnt=0;
      vis.reset();
      for(int i=1;i<=n;i++){
        if(onuse[i]&&inner[i]==0)q.push(i),qm.push(i),cnt++,vis[i]=1;
      }
      while(q.size()){
        int u=q.front();q.pop();
        for(int v:dg[u]){
          inner[v]--;
          if(onuse[v]&&inner[v]==0){
            q.push(v);
            cnt++;
          }
        }
      }
      if(cnt==used){
        if(mused>used){
          ans.clear();
          while(qm.size()){
          	int u=qm.front();
          	qm.pop();
          	vis[u]=1;
          	for(int v:e[u]){
          		if(vis[v]){
          			ans.push_back(mkp(u,v));
          			continue;
							}
          		ans.push_back(mkp(u,v));
          		ie[v]--;
          		if(ie[v]==0)qm.push(v),vis[v]=1;
						}
					}
					mused=used;
        }
      }
    }
    if(ans.size()!=0){
      cout<<ans.size()<<'\n';
      for(pair<int,int> pii:ans){
        cout<<pii.first<<' '<<pii.second<<'\n';
      }
    }
    else{
      cout<< -1<<'\n';
    }
  }

  
  return 0;
}

这东西竟然是对的。

其它部分分直接没来的及去想。

考虑正确的构造,我们把乘客的需求看成有向边,城市看成点,首先两个没有边连的城市集合当然一点关系都没有,形式化的说就是我们考虑每一块城市形成的弱连通块就行了。

连成的连通块可以是个 DAG。

是 DAG 好说,考虑拓扑排序的过程,构造发射顺序即可,而且这样也一定会用入度不为 \(0\) 的点的数量这么多的大炮。

如果不是 DAG 呢?我们发现 DAG 非常爽,于是我们也想让这个一团乱麻的有向图变成 DAG。

现在问题是我们要从哪个点开始发射,开始发射要用机会,于是我们可以把要发射到的节点挨个试一遍,发射完相当于删了这一条边,如果图变成了个 DAG 就行了。

于是可以暴力枚举这个边,这题就完了。

看着也不难啊。被 T1 区分了。

T3

首先有结论就是我们只会隔离一次,且是第一次走就要隔离。

因为我们隔离的时间只跟到的点有关系,那既然要隔离两次为什么第一次不直接去呢。

于是我们首先把风险度排序,如果询问是从小风险到大风险,直接过去就是,答案是 \(1\)

如果从大风险到小风险,排完序后就是从右到左走,我们可以选择走很长然后隔离一下,或者一点一点挪过去。

当然,我们隔离后如果到的点风险小于目标点,可以直接用 \(1\) 天过去,如果还大于,只能再挪过去了。因为隔离时间没有规律,因此这两种都有可能。算上上面的就是三种情况。

考虑怎么速算这三种情况,首先一点点挪我们肯定是要挪的越多越好,每个点移动的左右边界都是可以速算的。

因为我们现在讨论大风险到小风险的情况,因此只需要往左移就好了,我们可以用倍增维护 \(2^i\) 移动后,最左到的点的位置。

如果还要隔离,因为我们隔离的时间只跟到的点有关系,这里有个重要的处理就是我们不再关心从哪个点来了。直接尝试用 DP 维护这个事情,具体就是设 \(dp_i\) 表示随便什么点通过一次隔离+几次挪动到 \(i\) 点的最小天数。

转移是从右往左转移,首先这个点可以直接被跳到,或者从右边可以一次挪到自己的点转移,也就是找右边能一次挪到自己的点的 \(dp_j\) 的最小值,\(+1\) 后转移即可。

于是这个东西可以数据结构维护吧。

考虑答案计算,也就是直接到的,跑到前面跳回去的,一次次挪过去的三者取最小即可。第二个也可以用数据结构维护。

综合时间复杂度是 \(O(n\log n)\) 常数稍大。

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e6+5887;
const int lgr=24;
int w[N];
int tr[N];
int minarr[N];
struct work{
  int id;
  int w;
}wk[N];
bool cmp(work x,work y){
  return x.w<y.w;
}
int irr[N];
int fa[N][26];
vector<int> vc;
struct seg{
  int l;
  int r;
  int mv;
}t[N<<2];
void b(int p,int l,int r){
  t[p].l=l;t[p].r=r;
  if(l==r){
    t[p].mv=INT_MAX;
    return ;
  }
  int mid=l+r>>1;
  b(ls,l,mid);
  b(rs,mid+1,r);
  t[p].mv=min(t[ls].mv,t[rs].mv); 
  return ;
}
void modi(int p,int pos,int k){
  if(t[p].l==t[p].r&&pos==t[p].l){
    t[p].mv=k;return ;
  }
  int mid=t[p].l+t[p].r>>1;
  if(pos<=mid)modi(ls,pos,k);
  if(mid<pos) modi(rs,pos,k);
  t[p].mv=min(t[ls].mv,t[rs].mv);
  return ;
}
int qmin(int p,int l,int r){
  if(l<=t[p].l&&t[p].r<=r){
    return t[p].mv;
  }
  int mid=t[p].l+t[p].r>>1;
  if(l<=mid&&mid<r)return min(qmin(ls,l,r),qmin(rs,l,r));
  if(l<=mid)return qmin(ls,l,r);
  if(mid<r) return qmin(rs,l,r);
}
int main(){
  
  int n,q,m;cin>>n>>q>>m;
  for(int i=1;i<=n;i++){
    cin>>w[i];
    wk[i].w=w[i];
    wk[i].id=i;
  }
  sort(wk+1,wk+1+n,cmp);
  vc.push_back(INT_MIN);
  for(int i=1;i<=n;i++){
    irr[wk[i].id]=i;
    vc.push_back(wk[i].w);
  }
  for(int j=1;j<=n;j++)cin>>tr[j];
  for(int i=1;i<=n;i++){
    if(i==1){
      for(int j=0;j<=lgr;j++){
        fa[i][j]=1;
      }
      continue;
    }
    int k1=wk[i].w-m;
    int wp=lower_bound(vc.begin(),vc.end(),k1)-vc.begin();
    fa[i][0]=wp;
    for(int j=1;j<=lgr;j++){
      fa[i][j]=fa[fa[i][j-1]][j-1];
    }
  }
  b(1,1,n);
  minarr[n]=tr[wk[n].id]+1;
  modi(1,n,tr[wk[n].id]+1);
  for(int i=n-1;i>=1;i--){
    int minr=tr[wk[i].id]+1;
    int k1=wk[i].w+m;
    int wp=upper_bound(vc.begin(),vc.end(),k1)-vc.begin()-1;
    if(i<wp){
      minr=min(minr,qmin(1,i+1,wp)+1);
    }
    minarr[i]=minr;
    modi(1,i,minr);
  } 
  for(int i=1;i<=q;i++){
    int x,y;cin>>x>>y;
    if(irr[x]<irr[y]){cout<<1<<'\n';continue;}
    x=irr[x];y=irr[y];
    int ans=minarr[y];
    if(y-1>=1){
    	ans=min(ans,qmin(1,1,y-1)+1);
		}
    int mindr=0;
    bool f=0;
    for(int i=lgr;i>=0;i--){
      if(fa[x][i]>y){
        x=fa[x][i];
        mindr+=(1<<i);        
      }
    }
    if(fa[x][0]<=y)f=1,mindr++;
    if(f)cout<<min(ans,mindr)<<'\n';
    else cout<<ans<<'\n';
  } 
  
  return 0;
}

T4

数数题,放了。

二分一定要左闭右开开啊,不能再斜挂这个了。

posted @ 2025-11-23 23:56  hm2ns  阅读(8)  评论(0)    收藏  举报