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;
}
posted @ 2022-07-19 23:52  zyy2001  阅读(337)  评论(0编辑  收藏  举报