2022“杭电杯”中国大学生算法设计超级联赛(1)
比赛链接
2022“杭电杯”中国大学生算法设计超级联赛(1)
7139. Dragon slayer
给定一张 \(n\times m\) 的地图,有 \(k\) 道墙,每次操作可以消除一道墙,求由起点到终点的最小操作数
解题思路
bfs,二进制优化
关键在于图的位置表示,即点的坐标是小数,而墙的坐标是整数,将墙的横竖分别用两个数组表示,点左右/上下移动时用竖/横墙判断,二进制表示所有的墙是否操作,\(bfs\) 判断起点是否能到终点,注意点是否能经过墙的一些细节
- 时间复杂度:\(O(2^k\times nm)\)
代码
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=20;
int res,t,n,m,k,f[1<<15],dx[]={-1,0,1,0},dy[]={0,1,0,-1};
bool v[N][N],lr[N][N],ud[N][N];
struct A
{
int x[2],y[2];
}a[N];
PII s,e;
bool bfs(int st)
{
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)v[i][j]=lr[i][j]=ud[i][j]=0;
for(int i=0;i<k;i++)
if((st>>i&1)==0)
{
if(a[i].x[0]==a[i].x[1])
for(int j=a[i].y[0];j<a[i].y[1];j++)ud[a[i].x[0]][j]=1;
else
for(int j=a[i].x[0];j<a[i].x[1];j++)lr[j][a[i].y[0]]=1;
}
queue<PII> q;
q.push({s.fi,s.se});
v[s.fi][s.se]=1;
while(q.size())
{
auto t=q.front();
if(t==e)return true;
q.pop();
int x=t.fi,y=t.se;
if(x+1<n&&!v[x+1][y]&&!ud[x+1][y])
{
v[x+1][y]=1;
q.push({x+1,y});
}
if(x-1>=0&&!v[x-1][y]&&!ud[x][y])
{
v[x-1][y]=1;
q.push({x-1,y});
}
if(y+1<m&&!v[x][y+1]&&!lr[x][y+1])
{
v[x][y+1]=1;
q.push({x,y+1});
}
if(y-1>=0&&!v[x][y-1]&&!lr[x][y])
{
v[x][y-1]=1;
q.push({x,y-1});
}
}
return false;
}
int main()
{
help;
for(cin>>t;t;t--)
{
cin>>n>>m>>k;
cin>>s.fi>>s.se>>e.fi>>e.se;
for(int i=0;i<k;i++)
{
cin>>a[i].x[0]>>a[i].y[0]>>a[i].x[1]>>a[i].y[1];
if(a[i].x[0]==a[i].x[1])
{
if(a[i].y[0]>a[i].y[1])swap(a[i].y[0],a[i].y[1]);
}
else if(a[i].x[0]>a[i].x[1])swap(a[i].x[0],a[i].x[1]);
}
for(int i=0;i<(1<<k);i++)f[i]=0;
res=k;
for(int i=0;i<(1<<k);i++)
{
if(f[i])continue;
if(bfs(i))
{
res=min(res,__builtin_popcount(i));
for(int j=0;j<k;j++)f[i|1<<j]=1;
}
}
cout<<res<<'\n';
}
return 0;
}
7140. Backpack
给出 \(n\) 个物品,容量为 \(m\),体积和价值分别为 \(v_i,w_i\) ,求恰好将背包装满的价值最大异或和
解题思路
dp,bitset优化dp
-
状态表示:\(f[i][j][k]\) 表示前 \(i\) 个物品是否存在异或和为 \(j\),体积为 \(k\) 的情况
-
状态计算:\(f[i][j][k]=f[i-1][j][k]|f[i-1][j\bigoplus w][k-v]\)
显然可以滚动掉第一维,即每次求解 \(f[j][k]|f[i-1][j\bigoplus w][k-v]\),可由 \(bitset\) 优化,而 \(bitset\) 可以一维一维处理,所以另外开一个 \(bitset\) 数组,表示上一个数组,同时先表示出体积,即 \(k=k-v\),在 \(bitset\) 上对应左移 \(v\) 位
- 时间复杂度:\(O(n^3/64)\)
代码
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=1024;
bitset<N> f[N],g[N];
int n,m,t;
int main()
{
help;
for(cin>>t;t;t--)
{
cin>>n>>m;
for(int i=0;i<N;i++)f[i].reset();
f[0][0]=1;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=0;j<N;j++)g[j]=f[j],g[j]<<=v;
for(int j=0;j<N;j++)f[j]|=g[j^w];
}
int res=-1;
for(int i=0;i<N;i++)
if(f[i][m])res=i;
cout<<res<<'\n';
}
return 0;
}
7141. Ball
给定 \(n\) 个点,问有多少种方案,使得选出 \(3\) 个点中边的长度的中位数为素数
解题思路
思维,bitset优化
先找出为素数的边,对于这样的边,只需找出有多少点到该边两端点的距离一大一小,可以先将所有边排序,这样找的小边一定在前面,用 \(bitset\) 优化,设素数边两端点为 \(x,y\),\(c[x][z]\) 表示点 \(x\) 和 点 \(z\) 是否遍历过,即是否 \(|xz|\) 是否是小边,同理有 \(c[y][z]\),如果 \(c[x][z]\) 和 \(c[y][z]\) 一个为 \(0\) 一个为 \(1\),即 \(c[x][z]\bigoplus c[y][z]=1\),即一大一小的边,则 \(z\) 满足条件,所有 \(x,y\) 对答案的贡献为 \(c[x]\bigoplus c[y]\) 中 \(1\) 的数量
- 时间复杂度:\(O(n^3/64)\)
代码
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=2005,M=2e5+5;
int n,m,t;
bool v[M];
PII a[N];
bitset<N> c[N];
struct A
{
int x,y,d;
}b[N*N];
int main()
{
help;
v[1]=true;
for(int i=2;i<M;i++)
{
if(v[i])continue;
for(int j=i+i;j<M;j+=i)v[j]=true;
}
for(cin>>t;t;t--)
{
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++)
{
cin>>a[i].fi>>a[i].se;
for(int j=1;j<i;j++)b[++cnt]={i,j,abs(a[i].fi-a[j].fi)+abs(a[i].se-a[j].se)};
}
sort(b+1,b+1+cnt,[](A &a,A &b){return a.d<b.d;});
for(int i=0;i<N;i++)c[i].reset();
LL res=0;
for(int i=1;i<=cnt;i++)
{
int x=b[i].x,y=b[i].y;
if(!v[b[i].d])
res+=(c[x]^c[y]).count();
c[x][y]=c[y][x]=1;
}
cout<<res<<'\n';
}
return 0;
}
7146. Laser
棋盘中有 \(n\) 个点,问是否存在一个点,其按米字形朝 \(8\) 个方向射出的光线能否包含所有点
解题思路
思维,计算几何
假设存在这样的点,假设第一个点在 米字形竖线/横线/正对角线/反对角线,枚举其他点,一旦与第一个点不同在 米字形竖线/横线/正对角线/反对角线 上,则可以确定米字形中心,进而判断是否满足要求
- 时间复杂度:\(O(12\times n)\)
代码
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=5e5+5;
int t,n;
PII point[N],back[N];
bool pd(int x,int y)
{
return x==0||y==0||x==y||x==-y;
}
bool ck(int x,int y)
{
for(int i=1;i<=n;i++)
if(!pd(x-point[i].fi,y-point[i].se))return false;
return true;
}
bool ck()
{
bool flag=true;
for(int i=2;i<=n;i++)
{
if(point[i].fi==point[1].fi)continue;
flag=false;
if(ck(point[1].fi,point[i].se))return true;
if(ck(point[1].fi,point[i].se-(point[i].fi-point[1].fi)))return true;
if(ck(point[1].fi,point[i].se+(point[i].fi-point[1].fi)))return true;
}
return flag;
}
int main()
{
for(scanf("%d",&t);t;t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&back[i].fi,&back[i].se);
bool f=false;
//竖线
for(int i=1;i<=n;i++)point[i]=back[i];
if(ck())f=true;
//横线
for(int i=1;i<=n;i++)point[i]={back[i].se,back[i].fi};
if(ck())f=true;
//正对角线
for(int i=1;i<=n;i++)point[i]={back[i].fi-back[i].se,back[i].fi+back[i].se};
if(ck())f=true;
//反对角线
for(int i=1;i<=n;i++)point[i]={back[i].fi+back[i].se,back[i].fi-back[i].se};
if(ck())f=true;
puts(f?"YES":"NO");
}
return 0;
}
7149. Alice and Bob
有 \(a_i\) 个 \(i\)(\(0\leq i\leq n\)),\(Alice\) 每次可以将这些数划分为两个集合,\(Bob\) 每次可以删除一个集合,同时另外一个集合所有数减一。如果任何时候存在 \(0\) 则 \(Alice\) 胜,否则 \(Bob\) 胜
解题思路
博弈论
需要对 \(Alice\) 构造一种必胜策略,考虑将 \(a_i\) 个 \(i\) 平分,即每次平分进两个集合,无论 \(Bob\) 如何选择,而由于每次平分后另外一个集合所有数减一,相当于将 \(i\) 减一,逆序处理所有的 \(a_i\),累计所有贡献,只要判断最后轮次的时候是否还存在数即可,因为划分集合后选择权在 \(Bob\) 手上,如果 \(Alice\) 不采取平分的策略,\(Bob\) 一定会删除对自己最有利的集合,而如果采取平分策略,即主动权掌握在 \(Alice\) 手上,无论 \(Bob\) 如何操作,其结果都是一定的。另一种理解:平分策略的任一子集是非平分策略的两个子集的中和,如果采取非平分策略,\(Bob\) 可以删除使对 \(Alice\) 平分策略更差的子集,这样可能平分策略能获胜但有一步采取非平分策略结果却败了,故平分策略是最优策略
- 时间复杂度:\(O(n)\)
代码
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=1e6+5;
int a[N],n,t;
int main()
{
help;
for(read(t);t;t--)
{
cin>>n;
for(int i=0;i<=n;i++)cin>>a[i];
for(int i=n;i>=1;i--)a[i-1]+=a[i]/2;
puts(a[0]?"Alice":"Bob");
}
return 0;
}