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;
}