A
problem
有一个长度为 n n n 的括号串,你可以交换两个相邻的括号直到括号串合法(保证一定有合法解),请求出最小的操作次数。
数据范围: n ≤ 1 0 6 n\leq10^6 n≤106
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 ⌈2−ai⌉ 次单点加操作即可。答案就是对每个 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 n≤20
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(1≤i≤n) 个整数为 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} n≤106,0≤ai<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+1−1 。
因为 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 n≤2×105,m≤1.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;
}