zr23noip10连day1

文章讨论了四个编程问题:1)求合法括号串最少交换次数;2)机器人在未知障碍物中的路径规划;3)黑板整数操作优化问题;4)仙人掌图的最小生成树权值计算。每个问题都涉及特定的算法和数据结构解决方案,如前缀和、线性基和分治策略。
摘要由CSDN通过智能技术生成

A

problem

有一个长度为 n n n 的括号串,你可以交换两个相邻的括号直到括号串合法(保证一定有合法解),请求出最小的操作次数。

数据范围: n ≤ 1 0 6 n\leq10^6 n106

solution

括号序列的处理是很套路的 ( ( ( 1 1 1 ) ) ) − 1 -1 1 ,计算序列的前缀和 s u m i sum_i sumi

考虑怎样的交换是有贡献的。显然只有将 ) ( )( )( 交换为 ( ) () () 会对 s u m i sum_i sumi 造成 2 2 2 的贡献。

最终如果括号串合法则每个 s u m i sum_i sumi ≥ 0 \geq 0 0 ,那么不难发现对于每个 ≤ 0 \leq 0 0 s u m i sum_i sumi , 我们对其进行 ⌈ − a i 2 ⌉ \lceil \frac{-a_i}{2}\rceil 2ai 次单点加操作即可。答案就是对每个 i i i 求和。

时间复杂度 O ( n ) O(n) O(n)

code

//zr23noip10连day1_A
#include<bits/stdc++.h>
using namespace std;

#define int long long
const int NUM=1e6+5;

int n;
char ch[NUM];
int a[NUM],sum[NUM];

signed main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

  cin>>n>>ch+1;
  for(int i=1;i<=n;++i)
  {
    if(ch[i]=='(')
      a[i]=1;
    else if(ch[i]==')')
      a[i]=-1;
  }
  for(int i=1;i<=n;++i)
    sum[i]=sum[i-1]+a[i];
  int ans=0;
  for(int i=1;i<=n;++i)
  {
    if(sum[i]<0)
      ans+=(-sum[i]-1)/2+1;
  }
  cout<<ans;

  return 0;
}

B

problem

有一个无限大的平面,机器人最初在(0,0)处,然后它会执行 n n n 个命令。如果有障碍物,机器人将停留在当前位置并继续执行下面的命令。目前已知命令,未知障碍物分布,请求出机器人最终有可能在的位置。

数据范围: n ≤ 20 n\leq20 n20

solution

数据范围启发我们可以直接对于每一步判断是否停下,再将所有的情况判断合法性。最终统计的时候把终点去重统计即可。

code

//zr23noip10连day1_B
#include<bits/stdc++.h>
using namespace std;

#define pii pair<int,int>
#define mpi make_pair
const int NUM=25;

struct node
{
  int x,y;
}a[(1<<20)+5];

queue<pii> q;

int n,cnt;
char ch[NUM];
bool g[NUM<<1][NUM<<1],vis[NUM<<1][NUM<<1];

int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};

inline bool cmp(node a,node b)
{
  if(a.x==b.x)
    return a.y<b.y;
  return a.x<b.x;
}

inline void dfs(int x,int y,int step)
{
  int flag=0;
  vis[x+20][y+20]=1;
  if(!q.empty())
    q.pop();
  for(int i=1;i<=n;++i)
  {
    int tx=x,ty=y;
    if(ch[i]=='L')
      tx+=dx[0],ty+=dy[0];
    else if(ch[i]=='R')
      tx+=dx[1],ty+=dy[1];
    else if(ch[i]=='D')
      tx+=dx[2],ty+=dy[2];
    else if(ch[i]=='U')
      tx+=dx[3],ty+=dy[3];
    if(step&(1<<(i-1)))
    {
      if(g[tx+20][ty+20]) 
        flag=1;
      x=tx,y=ty;
      vis[tx+20][ty+20]=1;
      q.push(mpi(tx+20,ty+20));
    }
    else
    {
      if(vis[tx+20][ty+20])
        flag=1;
      g[tx+20][ty+20]=1;
      q.push(mpi(tx+20,ty+20));
    }
  }
  while(!q.empty())
  {
    int x=q.front().first,y=q.front().second;
    q.pop();
    g[x][y]=vis[x][y]=0;
  }
  if(flag)
    return ;
  a[++cnt].x=x,a[cnt].y=y;
}

signed main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

  cin>>n>>ch+1;
  for(int i=0;i<(1<<n);++i)
    dfs(0,0,i);
  sort(a+1,a+1+cnt,cmp);
  int tot=1;
  for(int i=2;i<=cnt;++i)
  {
    if(!(a[i].x==a[i-1].x && a[i].y==a[i-1].y))
      ++tot;
  }
  cout<<tot<<'\n';
  cout<<a[1].x<<" "<<a[1].y<<'\n';
  for(int i=2;i<=cnt;++i)
  {
    if(!(a[i].x==a[i-1].x && a[i].y==a[i-1].y))
      cout<<a[i].x<<" "<<a[i].y<<'\n';
  }

  return 0;
}

C

problem

黑板上写着 n 个非负整数,其中第 i ( 1 ≤ i ≤ n ) i (1\leq i\leq n) i(1in) 个整数为 a i a_i ai 。每次你可以选择两个当前写在黑板上的非负整数 x x x y y y ,将他们擦掉,并在黑板上额外写上 x x x 异或 y y y

并且在任意时刻,你可以选择当前写在黑板上的任意一个整数 x x x ,将他擦掉,并将 x + 1 x + 1 x+1 写在黑板上。你必须进行恰好一次这种操作。

你希望不断进行上述操作,直到黑板上最终只剩下一个整数。求最终剩下的整数最大值。

数据范围: n ≤ 1 0 6 , 0 ≤ a i < 2 60 n\leq10^6 , 0\leq a_i <2^{60} n106,0ai<260

solution

考虑如果没有 + 1 +1 +1 操作。则最终剩下的整数值固定。

尝试将 + 1 +1 +1 操作转换成异或操作。 不难想到若用 k k k 表示二进制上 x x x 末尾 1 1 1 的个数, + 1 +1 +1 导致二进制上进了一位,所以 + 1 +1 +1 操作等价于异或上 2 2 k + 1 − 1 2^{2k+1}-1 22k+11

因为 k k k 只有 l o g ω log \omega logω 种,因此考虑枚举 k k k ,然后判定是否能找到一个子集异或和的最低 z z z 位是 1 1 1 ,第 z + 1 z+1 z+1 位是 0 0 0 。造出这个数之后 + 1 +1 +1 再异或上其他数即可。

判定可以利用线性基,与常规线性基不同,因为最高的若干位可以随意。所以线性基应该从低到高建。

时间复杂度 O ( n l o g ω + l o g 2 ω ) O(n log \omega + log^2 \omega) O(nlogω+log2ω)

code

//zr23noip10连day1_C
#include<bits/stdc++.h>
using namespace std;

#define int long long
const int NUM=1e6+5;

int n,cnt,ans;
int a[NUM],xxj[65][65];

inline void insert(int x,int key)
{
  if(!key)
  {
  	for(int i=60;i>=0;--i)
  	{
  	  if(x&(1ll<<i))
  	  {
  	  	if(!xxj[key][i])
  	  	{
  	  	  xxj[key][i]=x;
  	  	  break;
		    }
		    else
		    {
		    	x^=xxj[key][i];
		    	if(!x)
		    		break;
				}
	    }
	  }
		return ;
  }
  for(int i=key;i>=0;--i)
  {
  	if(x&(1ll<<i))
  	{
  		if(!xxj[key][i])
	  	{
	  	  xxj[key][i]=x;
	  	  break;
	    }
	    else
	    {
	    	x^=xxj[key][i];
	    	if(!x)
	    		break;
			}
		}
	}
}

inline bool query(int x,int key)
{
	for(int i=key;i>=0;--i)
  {
  	if(x&(1ll<<i))
  	{
  		if(!xxj[key][i])
	  		return 0;
	    x^=xxj[key][i];
		}
	}
	return 1;
}

signed main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
	
	cin>>n;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
		cnt^=a[i];
		insert(a[i],0);
	}
	for(int i=1;i<=61;++i)
		for(int j=0;j<=60;++j)
			insert(xxj[0][j]&((1ll<<i)-1),i);
	ans=cnt+1;
	for(int i=1;i<=61;++i)
	{
		if(query((1ll<<(i-1))-1,i))
			ans=max(ans,cnt^((1ll<<(i-1))-1)^(1ll<<(i-1)));
	}
	cout<<ans;
	
  return 0;
}

D

problem

给定一个 n n n 个节点的仙人掌,每条边上有两个权值 a i , b i ai,bi ai,bi 和两个端点 u i , v i ui,vi ui,vi ,输出其最小生成树的权值(一个生成树的权值是 ∑ a i × ∑ b i \sum ai × \sum bi ai×bi )。

preposition

1、仙人掌的生成树等价于对于每个环独立的断一条边。

2、两个凸包的闵可夫斯基和等价于两个凸包的所有边按极角排序后顺次首尾相连形成的图形。

数据范围: n ≤ 2 × 1 0 5 , m ≤ 1.5 n , m a x ( a i , b i ) ≤ 1 0 4 n\leq 2\times10^5 , m\leq1.5n , max(a_i , b_i)\leq10^4 n2×105,m1.5n,max(ai,bi)104

solution

转换一下,我们考虑什么样的 ∑ a , ∑ b \sum a , \sum b a,b 会成为答案。

把每个生成树都写成一个平面上的点 ( ∑ a , ∑ b ) (\sum a , \sum b) (a,b) ,变成要求最小的一个 ans,使 f ( x ) = a n s x f(x)=\frac{ans}{x} f(x)=xans 上有点。显然只有在凸包上的点才可能成为答案。

考虑原图是棵仙人掌。仙人掌的生成树等价于对于每个环独立的断一条边,随即发现两个短边之后的环的合并形式正是闵可夫斯基和,因此直接对于每个环建立凸包,然后两个环合并就求闵可夫斯基和,合并多个环就分治即可。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

code

//zr23noip10连day3_D
#include<bits/stdc++.h>
using namespace std;

int n,m;
int a[65][65],b[65][65],f[2][35][35][65][65],g[2][35][35][65][65],ans[65];

signed main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

  cin>>n>>m;
  for(int i=1;i<=n;++i)
    for(int j=0;j<=m;++j)
      cin>>a[i][j];
  for(int i=1;i<=n;++i)
    for(int j=0;j<=m;++j)
      cin>>b[i][j];
  memset(f[0],-0x3f3f3f3f,sizeof(f[0]));
  f[0][0][0][0][0]=0;
  for(int i=1;i<=n;++i)
  {
    int r=(i&1),l=(!r);
    memset(f[r],-0x3f3f3f3f,sizeof(f[r]));
    for(int xx1=0;xx1<i;++xx1)
    {
      for(int yy1=0;yy1<=xx1;++yy1)
      {
        for(int xx2=0;xx2+xx1<=m;++xx2)
        {
          for(int yy2=0;yy2<=xx2;++yy2)
          {
            if(f[l][xx1][yy1][xx2][yy2]>=0)
            {
              for(int j=0;j+xx1+xx2<=m;++j)
              {
                f[r][xx1][yy1][xx2+j][yy2]=max(f[r][xx1][yy1][xx2+j][yy2],f[l][xx1][yy1][xx2][yy2]+a[i][j]);
                f[r][xx1+1][yy1][xx2+j][yy2]=max(f[r][xx1+1][yy1][xx2+j][yy2],f[l][xx1][yy1][xx2][yy2]+a[i][j+1]);
                f[r][xx1+1][yy1+1][xx2+j][yy2+j]=max(f[r][xx1+1][yy1+1][xx2+j][yy2+j],f[l][xx1][yy1][xx2][yy2]+a[i][j+1]);
              }
            }
          }
        }
      }
    }
  }
  memset(g[0],-0x3f3f3f3f,sizeof(g[0]));
  g[0][0][0][0][0]=0;
  for(int i=1;i<=n;++i)
  {
    int r=(i&1),l=(!r);
    memset(g[r],-0x3f3f3f3f,sizeof(g[r]));
    for(int xx1=0;xx1<i;++xx1)
    {
      for(int yy1=0;yy1<=xx1;++yy1)
      {
        for(int xx2=0;xx2+xx1<=m;++xx2)
        {
          for(int yy2=0;yy2<=xx2;++yy2)
          {
            if(g[l][xx1][yy1][xx2][yy2]>=0)
            {
              for(int j=0;j+xx1+xx2<=m;++j)
              {
                g[r][xx1][yy1][xx2+j][yy2]=max(g[r][xx1][yy1][xx2+j][yy2],g[l][xx1][yy1][xx2][yy2]+b[i][j]);
                g[r][xx1+1][yy1][xx2+j][yy2]=max(g[r][xx1+1][yy1][xx2+j][yy2],g[l][xx1][yy1][xx2][yy2]+b[i][j+1]);
                g[r][xx1+1][yy1+1][xx2+j][yy2+j]=max(g[r][xx1+1][yy1+1][xx2+j][yy2+j],g[l][xx1][yy1][xx2][yy2]+b[i][j+1]);
              }
            }
          }
        }
      }
    }
  }
  for(int i=0;i<=n;++i)
  {
    for(int yy1=0;yy1<=i;++yy1)
    {
      for(int xx2=0;xx2<=m-i;++xx2)
      {
        for(int yy2=0;yy2<=xx2;++yy2)
        {
          for(int j=0;j<=xx2;++j)
          {
            if(j+yy2>=xx2)
              ans[i]=max(ans[i],f[n&1][i][yy1][xx2][yy2]+g[n&1][i][i-yy1][xx2][j]);
          }
        }
      }
    }
  }
  for(int i=1;i<=n;++i)
    cout<<ans[i]<<" ";

  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值