NOIP模拟试题2021/11/11

T1

对于要走到终点,如果无法直接走到,我们就要在环上绕,由于绕一个环可能会有多余的部分绕,对于一条能够到达终点的路径,途中遇到还不能走的边,我们就要去绕环,所以我们考虑找到每条能够到终点的路径的要解锁的边的最大值,并找到每个点的能绕的最小环的边数,计算即可。
我想法是:
先用tarjan算出每个点属于的强连通,然后再bfs在这个强连通里找它的最小的环,然后再dfs找路。
不过WA了2个点。qwq蒟蒻
代码实现:

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

#define num ch-'0'
void get(int &res){
    char ch;bool flag=0;
    while(!isdigit(ch=getchar()))
        (ch=='-')&&(flag=true);
    for(res=num;isdigit(ch=getchar());res=res*10+num);
    	(flag)&&(res=-res);
}

const int N=505,M=25005,inf=0x3f3f3f3f3f3f3f3f;
int n,m,ans=inf,huan[N],dep[N];
int dfn[N],low[N],cnt,id[N],len[N],sum;
int first[N],nex[M],to[M],w[M],tot;
bool vis[N];
stack<int>a;
struct node{
	int p,e;
}pre[M];

void add(int x,int y,int z){
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}

void tar(int x){
	dfn[x]=low[x]=++cnt;
	a.push(x);
	vis[x]=1;
	for(int i=first[x];i;i=nex[i]){
		int y=to[i];
		if(dfn[y]==0){
			tar(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y])
			low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		int t;
		sum++;
		do{
			t=a.top();
			a.pop();
			id[t]=sum;
			len[sum]++;
			vis[t]=0;
		}while(t!=x);
	}
}

void bfs(int u){
	memset(vis,0,sizeof(vis));
	memset(dep,0,sizeof(dep));
	queue<int>q;
	int flag=id[u];
	q.push(u);
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int i=first[x];i;i=nex[i]){
			int y=to[i];
			if(id[y]==flag){
				dep[y]=dep[x]+1;
				if(y==u) {huan[u]=dep[y];return;}
				if(vis[y]==0){
					q.push(y);
					vis[y]=1;
				}
			}
		}
	}
}

void check(){
	int cntt=0,Min=inf,summ=0;
	for(int i=1;i!=0;i=pre[i].p){
		if(pre[i].p!=0){
			if(huan[i]) Min=min(Min,huan[i]);
			if(w[pre[i].e]>cntt) summ=max(summ,w[pre[i].e]-cntt);
			cntt++; 
		}
	}
	int s=ceil(summ*1.0/Min)*Min+cntt;
	ans=min(ans,s);
}

void dfs(int x){
	if(x==n) {check();return;} 
	vis[x]=1;
	for(int i=first[x];i;i=nex[i]){
		int y=to[i];
		if(!vis[y]) pre[x].p=y,pre[x].e=i,dfs(y);
	}
	vis[x]=0;
}

signed main(){
	//freopen("f.in","r",stdin);
	//freopen("f.out","w",stdout);
	get(n);get(m);
	int x,y,z;
	for(int i=1;i<=m;i++){
		get(x);get(y);get(z);
		add(x,y,z);
	}
	for(int i=1;i<=n;i++)
		if(dfn[i]==0) tar(i);
	for(int i=1;i<=n;i++) bfs(i);
	memset(vis,0,sizeof(vis));
	dfs(1);
	cout<<ans;
	return 0;
}

正解:
正解需要一点矩阵的前置知识,将用一篇新博客介绍。

T2

构造题,我们不妨全部 m o d    n \mod n modn而并不改变矩阵的关系,我们按顺序写出矩阵:
1,2,3,4,5,…n-1,0
1,2,3,4,5,…n-1,0
1,2,3,4,5,…n-1,0
1,2,3,4,5,…n-1,0
1,2,3,4,5,…n-1,0
1,2,3,4,5,…n-1,0
不难发现每一列已经是正确的了,而每一行计算后得到为 n + 1 2 \frac{n+1}{2} 2n+1
所以我们要保证 n + 1 n+1 n+1是偶数。
对于n为奇数的情况,
每一行每一列按顺序时已经好了,直接按顺序输出即可。
对于n为偶数的情况,
我们以n=4为例:
1,2,3,0
1,2,3,0
1,2,3,0
1,2,3,0
我们交换3,5,那么每一行就分别加2,减2,即加减 n 2 \frac{n}{2} 2n,运算后即可得到对于平均数加减了 1 2 \frac{1}{2} 21那么就配好。
但是这样原本好了的每一列就加减了 1 2 \frac{1}{2} 21,变得不正确了。
所以我们发现:
由于我们每次要交换2行,分情况讨论:
对于是4的倍数的情况,交换 n 2 \frac{n}{2} 2n次,正好 n 2 \frac{n}{2} 2n是偶数,行列便能配好。
所以如果是4的倍数,
我们仅需要交换每两行的第 n 2 + 1 \frac{n}{2}+1 2n+1个和第1个。

对于是2的倍数而不是4的倍数的情况,便不满足了,交换后最后的2行会剩下来。
所以我们要特殊的处理。
例如n=6时,
1,2,3,4,5,0
1,2,3,4,5,0
1,2,3,4,5,0
1,2,3,4,5,0
1,2,3,4,5,0
1,2,3,4,5,0
前面4行可以按上面的办法交换,对于最后2行,我们发现:
由于一直交换的第1列和第4列,所以其实一直影响的第1列和第4列,那么其他列是没有任何影响的,那我们不妨去换另外的列交换。例如对于最后两行交换第2列和第5列,由于第1行的之间交换是不会改变行的,那么我们就更改这两行即可。
对此,我们也发现了排列的方式是不唯一的,选择交换的不同,排列也不同。
注意:特判n=2时是无解的。
代码实现:

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

const int N=2005;
int n;
int a[N][N];

int main(){
	scanf("%d",&n);
	int num=n/2+1;
	if(n==2) {cout<<-1;return 0;}
	if(n%2==1){
		int cnt=0;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++) cnt++,cout<<cnt<<" ";
			cout<<"\n";
		}
		return 0;
	}
	if(n%2==0){
		int cnt=0;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++) cnt++,a[i][j]=cnt;
		if(n%4==0){
			for(int i=1;i<=n;i+=2) swap(a[i+1][1],a[i][num]);
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++) cout<<a[i][j]<<" ";
				cout<<"\n";
			}
			return 0;
		}
		else{
			for(int i=1;i<=n-2;i+=2) swap(a[i+1][1],a[i][num]);
			swap(a[n][2],a[n-1][num+1]);
			swap(a[1][2],a[1][num+1]);
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++) cout<<a[i][j]<<" ";
				cout<<"\n";
			}
			return 0;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值