2022杭电多校联赛第一场 题解

比赛传送门
作者: fn


签到题

1011题 Random / 随机

题目大意
[ 0 , 1 ] [0,1] [0,1] 之间随机生成 n n n 个数字
进行 m m m 次运算, 1 / 2 1/2 1/2 概率删除最大值, 1 / 2 1/2 1/2 概率删除最小值
计算期望值之和,对 1 0 9 + 7 10^9+7 109+7 取模

考察内容
数学知识

分析
显然,可以把每个数看作0.5,答案就是 ( n − m ) / 2 (n-m)/2 (nm)/2
除法用乘法逆元计算。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const ll mod=1e9+7;
ll n,m;

ll power(ll a,ll b){ // a^b%mod
	ll c=1;
	for(;b;b>>=1){
		if(b&1)c=c*a%mod;
		a=a*a%mod;
	}
	return c;
}

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n>>m;
		cout<<(n-m)*power(2,mod-2)%mod<<endl;
	}
	return 0;
}

基本题

1012题 Alice and Bob / 爱丽丝和鲍勃

题目大意
黑板上有 m m m 0 0 0 n n n 之间的整数。
游戏规则如下:
爱丽丝先手,如果黑板上还有数字,并且黑板上没有值为0的数字,爱丽丝可以将黑板上剩余的数字分成两组。
鲍勃后手,他选择其中一个集合并擦除该集合中的所有数字,然后将所有剩余的数字减去一。

在任何时候,如果黑板上有一个值为0的数字,爱丽丝获胜;否则,如果黑板上的所有数字都被删除,鲍勃获胜。
爱丽丝和鲍勃都采用最优策略,请确定谁将赢得比赛。

考察内容
博弈论,找规律

分析
找规律,如果 0 0 0 的个数 a [ 0 ] ≥ 1 a[0]≥1 a[0]1 ,则Alice直接获胜。
如果 1 1 1 的个数 a [ 1 ] ≥ 2 a[1]≥2 a[1]2 ,则Alice可以把其中两个1分到两个不同的集合中,从而获胜。
如果 1 1 1 的个数 a [ 1 ] ≥ 1 a[1]≥1 a[1]1 并且 2 2 2 的个数 a [ 2 ] ≥ 2 a[2]≥2 a[2]2,则Alice可以把1和两个2分到两个不同的集合中,从而获得一个0或者两个1,从而获胜。
如果 2 2 2 的个数 a [ 2 ] ≥ 4 a[2]≥4 a[2]4,同理,Alice可以获胜。

容易发现规律,2个 a [ i ] a[i] a[i] 可以变成1个 a [ i − 1 ] a[i-1] a[i1],反向遍历一遍,判断 a [ 0 ] a[0] a[0] 是否 ≥ 1 ≥1 1 即可

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n;
		for(int i=0;i<=n;i++){
			cin>>a[i];
		}
		
		for(int i=n;i>=1;i--){
			if(a[i]>=2){
				a[i-1]+=a[i]/2;
			} 
		}
		
		if(a[0]>=1){
			cout<<"Alice"<<endl;
		}
		else{
			cout<<"Bob"<<endl;
		}
	}
	return 0;
}
/*
1
1
0 2

*/ 

1002题 Dragon slayer / 屠龙者

题目大意
给定一个矩形区域。左下角为 ( 0 , 0 ) (0,0) (0,0),右上角为 ( n , m ) (n,m) (n,m)
起点是 ( x s + 0.5 , y s + 0.5 ) (x_s+0.5,y_s+0.5) (xs+0.5ys+0.5) ,终点是 ( x t + 0.5 , y t + 0.5 ) (x_t+0.5,y_t+0.5) (xt+0.5yt+0.5)

该区域有 k k k 堵水平或垂直的墙。你可以在区域内的任何方向移动,但不能穿过墙壁,包括墙壁的末端。

起点到终点可能会被墙挡住。你每次技能可以使一堵墙消失,求到达终点最少需要使用多少次技能。

1 ≤ n , m , K ≤ 15 1≤n, m, K≤15 1n,m,K15

考察内容
dfs,FloodFill算法

分析
地图大小全部*2,让人落在整数点上。每次走两格。
dfs枚举每堵墙是否存在,FloodFill从起点开始染色,通过终点是否被染色来判断是否能够到达终点,如果能够到达则更新答案(答案就是没选中的墙的数量)。
复杂度为 O ( 2 15 ∗ 1 5 2 ) O(2^{15}*15^2) O(215152) ,约 7 ∗ 1 0 6 7*10^6 7106 ,可以通过。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;

ll n,m,k;
ll xs,ys,xt,yt;
		
int mp[35][35]; // 是否有墙 
int color[35][35]; // 答案 

const int X[4]={2,0,-2,0},Y[4]={0,2,0,-2};

ll ans=0;

void fillcolor(ll x,ll y,ll num){
	if(color[x][y]<=num)return;
	color[x][y]=num;

	for(int i=0;i<4;i++){
		int dx=x+X[i]; int dy=y+Y[i];
		if(dx>=0 && dx<=n && dy>=0 && dy<=m){
			int cnt=0;
			if(mp[x+X[i]/2][y+Y[i]/2]){ // 要开墙 
				cnt=1; 
			}
			
			if(cnt==0){ // 不需要开墙 
				fillcolor(dx,dy,num+cnt);
			}
		}
	}
}

bool ck(){
	for(int i=1;i<=n;i+=2){
		for(int j=1;j<=m;j+=2){
			color[i][j]=1e9+10;
		}
	}
	fillcolor(xs,ys,0);
	if(color[xt][yt]==0){
		return 1; // 可以到达 
	}
	return 0;
}

ll x1[20],y1[20],x2[20],y2[20];

int note[20];
void dfs(ll x,ll y){
	note[y]=x;
	
	if(y==k){ // 递归边界 
		for(int i=0;i<=n;i++){
			for(int j=0;j<=m;j++){
				mp[i][j]=0; // 初始化mp 
			}
		}
		
		ll c1=0; 
		for(int i=1;i<=k;i++){
			if(note[i]==1){ // 选中这面墙 
				if(x1[i]==x2[i]){
					for(int j=y1[i];j<=y2[i];j++){
						mp[x1[i]][j]=1;
					}
				}
				else if(y1[i]==y2[i]){
					for(int j=x1[i];j<=x2[i];j++){
						mp[j][y1[i]]=1;
					}
				}
			}
			else{
				c1++; // 使用了技能,不选这面墙 
			}
		}
		
		if(ck()){
			ans=min(ans,c1); // 更新答案 
		}
		return;
	}	
	
	// y<k
	dfs(1,y+1);
	dfs(0,y+1);
}

int main(){ // AC. 每次可以让一整面墙消失 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n>>m>>k; 
		n<<=1; m<<=1;
		
		for(int i=0;i<=n;i++){
			for(int j=0;j<=m;j++){
				mp[i][j]=0;
			}
		}
		
		cin>>xs>>ys>>xt>>yt;
		ans=abs(xt-xs)+abs(yt-ys); // ans不超过两点间的曼哈顿距离 
		ans=min(ans,k); // ans不超过k 
		// ans<=k<=15
		
		xs=xs*2+1; ys=ys*2+1; xt=xt*2+1; yt=yt*2+1;
		
		for(int i=1;i<=k;i++){
			cin>>x1[i]>>y1[i]>>x2[i]>>y2[i];
			x1[i]<<=1; y1[i]<<=1;  
			x2[i]<<=1; y2[i]<<=1;  
		}
		
		dfs(0,0);
		
		cout<<ans<<endl;	
	}
	return 0;
}
/*
1
3 2 2 
0 0 2 1
0 1 3 1
1 0 1 2

1
3 2 2 
0 0 2 1
2 1 2 2
1 0 1 1
*/ 

进阶题

1009题 Laser / 激光

题目大意
二维平面上有 n n n 个敌人,第 i i i 个敌人的位置是 ( x i , y i ) (x_i, y_i) (xi,yi)

你现在有一个激光武器,你可以把它放在任何一点 ( x , y ) (x, y) (x,y) (x, y是实数)上,武器发射激光,把垂直、水平、左右斜线的敌人摧毁。

求是否可以摧毁所有敌人。

考察内容
数学知识,枚举,暴力

分析

方法一, O ( 4 ! n ) O(4!n) O(4!n)
直接暴力讨论。
假设第一个点是水平的,然后能囊括哪些点,然后下一个没囊括的点是竖直点之类,再下一个没囊括的点是左下到右上的斜线,以此类推。
四种方向,复杂度为 O ( 4 ! n ) O(4!n) O(4!n) 。常数只有24,不会tle。

方法二, O ( 4 ∗ 3 n ) ) O(4*3n)) O(43n))
选中一个点,讨论这个点的四种情况(垂直、水平、左右斜线)。
每种情况对应一条线,因为线上只有不在这条线上的点作出的线和这条线的交点可以选,所以只有 3 ∗ n 3*n 3n 个可选的中心点用于放置激光武器。
枚举每个中心点,复杂度为 O ( 4 ∗ 3 n ) ) O(4*3n)) O(43n))

方法一的代码如下:

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

typedef long long ll;
typedef pair<ll,ll> PII;
const int INF=0x3f3f3f3f;

inline ll read(){
    char ch;ll x=0;bool f=true;
    for(ch=getchar();!isdigit(ch);ch=getchar())if(ch=='-')f^=f;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
    return f?x:-x;
}
void solve(){ // 暴力讨论
    int n=read();
    vector<PII> s(n);
    vector<int> v={0,1,2,3,4};
    function<ll(ll,ll,ll)> check=[&](ll x,ll y,ll c)->ll{
        if(c==1)return x;
        if(c==2)return y;
        if(c==3)return x+y;
        return x-y;
    };
    for(int i=0;i<n;i++)
        s[i].first=read()*2,s[i].second=read()*2;
    function<PII(int,int,PII,PII)> _hack=[&](int a,int b,PII x,PII y)->PII{
        if(a>b)swap(a,b),swap(x,y);
        if(a==1&&b==2)return {x.first,y.second};
        if(a==1&&b==3)return {x.first,y.first+y.second-x.first};
        if(a==1&&b==4)return {x.first,x.first-(y.first-y.second)};
        if(a==2&&b==3)return {y.first+y.second-x.second,x.second};
        if(a==2&&b==4)return {(y.first-y.second)+x.second,x.second};
        if(a==3&&b==4)return {(x.first+x.second+y.first-y.second)/2,x.first+x.second-(x.first+x.second+y.first-y.second)/2};
    };
    do{
        int idx=0;
        vector<ll> p(5);
        vector<PII> c; 
        for(int i=0;i<=n;i++){
            if(i==n)return cout<<"YES\n",void();
            int flag=true;
            for(int j=1;j<=idx;j++){
                if(p[v[j]]==check(s[i].first,s[i].second,v[j]))
                    flag=false;
            }
            if(flag){
                idx++;
                c.push_back({s[i].first,s[i].second});
                if(idx>=5)break;
                p[v[idx]]=check(s[i].first,s[i].second,v[idx]);
                if(c.size()==2){
                    PII var=_hack(v[1],v[2],c[0],c[1]);
                    idx++;
                    p[v[idx]]=check(var.first,var.second,v[idx]);
                    idx++;
                    p[v[idx]]=check(var.first,var.second,v[idx]);
                }
            }
        }
    }while(next_permutation(v.begin()+1,v.end()));
    cout<<"NO\n";
}

int main(){
    int T=1;
    T=read();
    for(int i=1;i<=T;i++)solve();

    return 0;
}

1003题 Backpack / 背包

题目大意

爱丽丝有一个容量m的背包,她现在想装一些物品。她有 n n n 个物品,每个物品体积为 v i v_i vi ,价值为 w i w_i wi
是否可以选择若干个物品,使背包完全装满(即体积之和等于背包容量)?如果能装满,当背包已满时,背包中物品的最大异或和是多少?

不能装满输出-1,否则输出背包中物品的最大异或和。

考察内容
dp,bitset压位

分析
动态规划。
动态规划
对图中第二行状态转移方程的解释:
f i − 1 , j , k = 1 f_{i-1,j,k}=1 fi1,j,k=1 表示不拿第 i i i 个物品已经可以满足异或和为 j j j 且体积为 k k k f i − 1 , j ⊕ v i , k − w i = 1 f_{i-1,j ⊕v_i,k-w_i}=1 fi1,jvi,kwi=1 表示拿上第 i i i 个物品刚好可以满足异或和为 j j j 且体积为 k k k
不拿第 i i i 个物品已经可以满足或者拿上第 i i i 个物品刚好可以满足时,方案存在, f i , j , k = 1 f_{i,j,k}=1 fi,j,k=1;否则 f i , j , k = 0 f_{i,j,k}=0 fi,j,k=0

1 1 1 n n n 枚举 i i i ,枚举到 i = n i=n i=n 时,满足 f [ j ] [ m ] = 1 f[j][m]=1 f[j][m]=1 的最大的 j j j 即为最大值。详见代码。

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

int n,m,cnt;
bitset<1050> f[1050],g[1050];

void solve(){
	cin>>n>>m;
	for(int j=0;j<1024;++j){
		f[j].reset();
	}
	f[0][0]=1;
	int x,y;
	int ans=-1;
	for(int i=1;i<=n;++i){
		cin>>x>>y;
		for(int j=0;j<1024;++j){
			g[j]=f[j]; 
			g[j]<<=x; 
		}
		for(int j=0;j<1024;++j){
			f[j]|=g[j^y]; // f[j] = f[j] | g[j^y];
		}
	}
	
	for(int j=0;j<1024;++j){
		if(f[j][m]){
			ans=j;
		}
	}
	cout<<ans<<endl;
	return;
}

int main(){ // dp,bitset优化转移 
	std::ios::sync_with_stdio(false); cin.tie(0);
	int t=1;cin>>t;
	for(int i=1;i<=t;++i){
		solve();
	}
	return 0;
} 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值