【HNCPC2019】湖南省赛2019vp记录 部分题解

Posted on 2024-03-19 16:35  木易meow  阅读(43)  评论(0编辑  收藏  举报

开篇碎碎念

第一次组队完成的组队训练任务,感觉果然和个人赛不一样,唔有一种有人兜底的安全感bushi(虽然赛后还是需要补题//可恶)
赛时过了五题,然后补了两题))所以题解就写一下ABEFGIK好啦,嗯...其实是因为其他的题开不出来(小声...)

A.全1子矩阵

题意:

判断是不是这个n行m列的矩阵是不是存在x1,x2,y1,y2;使在这个范围内的都是1,其余地方都是0;
注意特判一下全0矩阵也是不合法的

解题思路:

从前往后遍历一下第一个出现1的行st,从后往前找一下最后一个出现1的行ed;
在st行里面找到第一个1出现的位置l,最后一个出现1的位置r
判断是否从st到ed的每一行l~r范围内都是1,范围外都是0

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=50;
const int mod=1e9+7;
const int inf=1e18;

int a[MAXN][MAXN];

void sol(int n,int m){
	string s;
	for(int i=1;i<=n;i++){
		cin>>s;
		for(int j=0;j<m;j++){
			a[i][j+1]=s[j]-'0';
		}
	}
	int ls=-1,rs=-1,st=-1;
	for(int i=1;i<=n;i++){//找到开始行的左右两个端点
		for(int j=1;j<=m;j++){
			if(a[i][j]==1){
				ls=j;
				break;
			}
		}
		if(ls>0){
			for(int j=m;j>=1;j--){
				if(a[i][j]==1){
					rs=j;
					break;
				}
			}
			st=i;
			break;
		}
	}
	int ed=-1;
	for(int i=n;i>=st;i--){//找结束行
		for(int j=1;j<=m;j++){
			if(a[i][j]==1){
				ed=i;
				break;
			}
		}
		if(ed>0)break;
	}
	// cerr<<ls<<' '<<rs<<endl;
	// cerr<<st<<' '<<ed<<endl;
	int f=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]){
				if(i<st || i>ed || j<ls || j>rs){
					f=0;break;
				}
			}
			else{
				if((i>=st && i<=ed) && (j>=ls && j<=rs)){
					f=0;
					break;
				}
			}
		}
		if(f==0)break;
	}
	if(st==-1 || ed==-1 || ls==-1 || rs==-1){
		cout<<"No"<<endl;
		return;
	}
	if(!f){
		cout<<"No"<<endl;
		return ;
	}
	else cout<<"Yes"<<endl;
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int x,y;
	while(cin>>x>>y){
		sol(x,y);
	}
	return 0;
}

B.组合数

题意:

给出n和k,输出Cnk和1e18的最小值

反思:

最一开始发现了前者是Cnk的组合数,于是想对于每个k,求出最小的满足乘积大于1e18的n;打了一个长长的表之后发小交上去WA了,突然意识到在紧贴1e18的地方,可能会存在在除分母之前的瞬间爆longlong,于是想用int128,但是int128变量类型的输入输出有些忘记...(一开始还以为是编译器坏掉了捏)
其实其实明明可以直接交python的,周天的牛客就有一道大数据的题目(虽然是猜结论的签到题)但是直接用python卡过去了(呜呜还不是因为用C++手写高精T了一直化简式子还是T),但是python的一行输入好几个数不太会,只依稀记得一个split,最后还是翻python自带的help才找到的。
赛时最后是用了一手long double去判断是不是大于int128,然后输出还是用的long long。
但是还是因为这个题牵扯了一下情绪(毕竟是签嘛长着一副签的样子)

思路:

就像在前面反思里提到的用了一下long double去判断

code:

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
const int MAXN=3e5+10;
const int mod=1e9+7;
#define inf 1000000000000000000
#define ld long double
#define int long long

int a[MAXN];

void sol(int n,int k){
	ld ans=1;
	int aa=1;
	if(k>n/2){
		k=n-k;
	}
	// cerr<<n<<' '<<k<<endl;
	for(int i=1;i<=k;i++){
		ans=ans*(n-i+1)/i;
		aa=ans;
		if(ans>=inf || ans<0){
			cout<<inf<<endl;
			return ;
		}
	}
	cout<<aa<<endl;
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	// t=1;
	int n,k;
	while(cin>>n>>k){
		sol(n,k);
	}
	return 0;
}

E.Numbers

题意:

给定一个长度不超过50的字符串s,判断s是否能由有限个互不相同的0到99中的数拼接而成

思路:

爆搜 因为字符串的长度不超过50,而且不超过100组,也就是说总和不超过5000,配上5s的时间限制,长着一副可以爆搜的样子。
分别讨论一下当前位置是否可以留1位,2位,判断能否保留两位的话需要判断一下是否是结尾(这里RE了一阵子)
注意剪枝:当1位2位均不能保留的时候return;

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=3e5+10;
const int mod=1e9+7;
const int inf=1e18;

string s;
int vis[105],n,ans;

void dfs(int pos){
	if(pos==n){
		ans++;
		return ;
	}
	int num=s[pos]-'0';
	int f=0;
	if(!vis[num]){//保留1位
		vis[num]=1;
		f=1;
		dfs(pos+1);
		vis[num]=0;
	}
	if(s[pos]!='0' && pos!=n-1){//保留两位
		num=(s[pos]-'0')*10+s[pos+1]-'0';
		if(vis[num] && !f){
			return ;
		}
		if(!vis[num]){
			vis[num]=1;
			dfs(pos+2);
			vis[num]=0;
		}
	}
}

void sol(){
	for(int i=0;i<100;i++){
		vis[i]=0;
	}
	n=s.size();
	ans=0;
	dfs(0);
	cout<<ans<<endl;
}

signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	// t=1;
	while(cin>>s){
		sol();
	}
	return 0;
}

F.4 Bottons

题意:

有四种按钮,分别可以向右移动不超过a步,向上移动不超过b步,向左移动不超过c步,向下移动不超过d步。总共可以按下不超过n次,问总共能到达多少个不同的坐标格

思路:

一个没有严谨证明的第六感:因为可以移动的都是不超过某个上限值,所以选左就没必要选右,选上就没必要选下
所以就分象限讨论,对于左上来说可以分1~(n-1)步给左,其余部分给向上(也就是b(n-k),所以可以转化成n-1个底边为c,高度为b(n-k)的小矩形求和
然后再将横纵轴上的相加即可

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=3e5+10;
const int mod=1e9+7;
const int inf=1e18;


void sol(int n){
	int a,b,c,d;
	cin>>a>>b>>c>>d;
	int ans=0;
	ans=(a*b%mod+b*c%mod+c*d%mod+a*d%mod)%mod;
	ans=ans*(n*(n-1)/2%mod)%mod;
	int tot=0;
	tot=(a+b+c+d)%mod;
	ans=ans+(tot*n)%mod;
	ans=(ans+1)%mod;
	cout<<ans<<endl;
}

signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	// t=1;
	int n,k;
	while(cin>>n){
		sol(n);
	}
	return 0;
}

G.字典序

题意:

给定一个矩阵,调整一下矩阵中列的顺序(这个顺序需要字典序尽量小),使得调整后的矩阵从逐行字典序不降

思路:

参考了组酥雨大大的博客,呜呜呜他好强
从两行的矩阵开始看起,对于一部分位置C1i < C2i,一部分大于,还有部分相等。影响字典序的是前两部分。题目要求的是前面的行字典序小,所以每个第二部分的前面都必须要至少有一个属于前者的。
那么也就是说要想使用后面的需要解锁一下限制...
分析一下限制,假如对于第三列来说,C13和C23都大于C33,那么也就是第三列必须解锁两次限制(即一定放了至少一个比第三行处在该位置的小的列)把前两行都排好之后才有可能排第三列;同样相邻的两行间可能有多对关系属于逆序的。所以是一个多对多的关系,考虑拓扑排序
所以分别记录了一下使用了第K列之后可以调整哪几行,以及每一行可以开哪些列的限制,每一列都还有几道限制未开。
由于题目要保证字典序最小,所以每次都要选择列号最小的,没有限制未开的列

code:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int a[MAXN][MAXN];
int lim[MAXN];//每一列的目前的限制数————拓扑排序中的出/入边数
vector<int>A[MAXN];//每一列可以导致哪几行顺序
vector<int>B[MAXN];//每行顺序可以解锁哪几列的一层封锁
int ans[MAXN];
int n,m;
void sol(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	memset(lim,0,sizeof lim);
	for(int i=1;i<n;i++)B[i].clear();
	for(int i=1;i<=m;i++)A[i].clear();
	for(int i=1;i<n;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]<a[i+1][j]){
				A[j].push_back(i);
			}
			else if(a[i][j]>a[i+1][j]){
				B[i].push_back(j);
				lim[j]++;
			}
		}
	}
	int cnt=0;
	for(int t=1;t<=m;t++){
		int p=0;
		for(int i=1;i<=m;i++){
			if(lim[i]==0){
				p=i;
				lim[i]=0x3f3f3f3f;
				break;
			}
		}
		if(p==0){
			break;
		}
		ans[++cnt]=p;
		for(auto i:A[p]){
			for(auto j:B[i]){
				lim[j]--;
			}
			B[i].clear();
		}
		A[p].clear();
	}
	if(cnt!=m){
		cout<<"-1"<<endl;
	}
	else{
		for(int i=1;i<=m;i++){
			cout<<ans[i]<<" \n"[i==m];
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	while(cin>>n>>m){
		sol();
	}
}

I.2019

题意:

给出n个点的一个树,每条边都有一个权值,找出所有满足u~v路径上权值和为2019倍数的点对(无序)

思路:

队里的OI✌:这不一眼淀粉质吗...但是由于我们两个都不会点分治,在py大大的现学现用下没过,于是我就树上dp试着搞了搞(最后十分钟过了!我靠!)
用dp[i][j]表示以i为根的子树中,距离i长度为j(mod 2019)的点的个数
dp[i][0]初始化为1,因为对于一个点到其本身距离为0
然后跑dfs就好了

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=2e4+10;
const int mod=2019;
const int inf=1e18;

vector<pair<int,int> >e[MAXN];
int dp[MAXN][mod];
int n,ans;

void dfs(int pos,int fa){
	dp[pos][0]=1;
	for(auto i:e[pos]){
		int v=i.first,w=i.second;
		if(v==fa)continue;
		dfs(v,pos);
		for(int j=0;j<=2018;j++){
			ans+=dp[v][j]*dp[pos][((2019-w-j)%2019+2019)%2019];//距离u为j,加上uv边,还差距离v点2019-w-j的
		}
		for(int j=0;j<=2018;j++){
			dp[pos][(j+w)%2019]+=dp[v][j];//更新一下距离根为各个模数的条数
		}
	}
}
void sol(){
	ans=0;
	for(int i=1;i<=n;i++){
		e[i].clear();
		for(int j=0;j<=2018;j++){
			dp[i][j]=0;
		}
	}
	int u,v,w;
	for(int i=1;i<n;i++){
		cin>>u>>v>>w;
		e[u].push_back({v,w});//加边
		e[v].push_back({u,w});//加边
	}
	dfs(1,-1);
	cout<<ans<<endl;
}

signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	// t=1;
	while(cin>>n){
		sol();
	}
	return 0;
}

K.双向链表练习题

题意:

模拟两步操作:把ab链表连接起来,然后进行翻转,赋值给a,同时将b链表清空

思路:

STL是个好东西
因为是A链表和B链表连接起来再翻转,发现这不就是相当于把B链表和A链表的反表连接起来了嘛,所以那就直接正反都存一下好了

code:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
int n,m;
list<int>lst[MAXN],rlst[MAXN];
void sol(){
	for(int i=0;i<=n;i++){
		lst[i].clear();
		rlst[i].clear();
	}
	for(int i=1;i<=n;i++){
		lst[i].push_back(i);
		rlst[i].push_back(i);
	}
	int a,b;
	while(m--){
		cin>>a>>b;
		rlst[a].splice(rlst[a].begin(),rlst[b]);
		lst[a].splice(lst[a].end(),lst[b]);
		swap(lst[a],rlst[a]);
	}
	cout<<lst[1].size()<<' ';
	for(auto i:lst[1]){
		cout<<i<<' ';
	}
	cout<<endl;
}
signed main(){
	while(cin>>n>>m){
		sol();
	}
}