题目链接:https://codeforces.com/contest/1206/problem/D
分析:
经过思考,很容易发现若同一位的 1 的数量 >= 3 ,则答案直接为3,否则点的数量不会超过(2*64,除0外)
然后直接求最小环就行了,比较常见的算法有 floyed 和 dfs.
floyed:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int N=1e3+5;
const int INF=1e8;
#define LL long long
int n,m,ans=INF;
int p[N],s[N];
LL a[N*N];
int d[N][N];
int f[N][N];
void floyed(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=d[i][j];
for(int k=1;k<=n;k++)
{
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
if(f[i][j]+d[j][k]+d[k][i]<ans)
ans=f[i][j]+d[j][k]+d[k][i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(f[i][j]>f[i][k]+f[k][j])
f[i][j]=f[i][k]+f[k][j];
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
scanf("%lld",a+i);
for(int i=1;i<=n;i++)
for(int j=0;j<63;j++)
if(a[i]&(1LL<<j))
p[j]++;
for(int i=0;i<63;i++)if(p[i]>=3)return puts("3"),0;
int len=0;
for(int i=1;i<=n;i++)
if(a[i])a[++len]=a[i];
n=len;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i]&a[j])
d[i][j]=1;
else
d[i][j]=INF;
floyed();
if(ans==INF)puts("-1");
else printf("%d\n",ans);
}
dfs思路很好想,却很容易写错(我就是问群友怎么写的,群友NB)
易错点:
1.图未联通,只搜第一个点
2.实现出错
最初很容易想到的一个思路是:从点1开始搜,然后记录他们进入的位置,然后一路搜下去(已经走过的点不在走),判断他们是否在走到这个点,走到这个点肯定是有环的,代码如下
void dfs(int u,int rd){
s[u]=rd;
for(int i=1;i<=n;i++)
if(d[u][i]){
if(s[i]){
if(s[u]-s[i]+1>=3)
ans=min(ans,s[u]-s[i]+1);
}
else
dfs(i,rd+1);
}
}
仔细分析会发现这个思路是有问题的,比如有一个图,如下
我首先搜点1,然后依次搜到 2 ,3,4,5,6,这时候就有一个答案5,记录,继续搜到 7,又有一个答案7,但是通过肉眼观察发现应该还有一个答案 4,但是并没有出现这个答案,为什么呢?
仔细分析发现原因是因为 7进入的位置应该是2的,但是因为一路搜过来,搜到他了,因此你后面不搜他,会漏掉很多答案,因此在吧7这个点搜一遍就行了。
dfs:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int N=1e3+5;
const int INF=1e9;
#define LL long long
int n,m,ans=INF;
int p[N],s[N];
LL a[N*N];
bool d[N][N];
void dfs(int u,int rd){
s[u]=rd;
//printf("%d %d\n",u,rd);
for(int i=1;i<=n;i++)
if(d[u][i]){
if(s[i] && s[u]-s[i]+1>=3)
ans=min(ans,s[u]-s[i]+1);
if(!s[i] || s[i]>rd+1)
dfs(i,rd+1);
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
scanf("%lld",a+i);
for(int i=1;i<=n;i++)
for(int j=0;j<63;j++)
if(a[i]&(1LL<<j))
p[j]++;
for(int i=0;i<63;i++)if(p[i]>=3)return puts("3"),0;
int len=0;
for(int i=1;i<=n;i++)
if(a[i])a[++len]=a[i];
n=len;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i]&a[j])
d[i][j]=1;
//printf("n=%d\n",n);
for(int i=1;i<=n;i++)
if(!s[i])dfs(i,1);
if(ans==INF)puts("-1");
else printf("%d\n",ans);
}
要将仙人掌变成树(或者森林),只需要保证对于仙人掌中的每个环,至少有一条边被删去即可。
设图中环的大小分别为 c1, c2, …, ck,不属于任何一个环的
边数为 b,则答案为
2 b ∏ i = 1 k ( 2 c i − 1 ) 2^{b}\prod_{i=1}^{k}(2^{c_{i}}-1) 2b∏i=1k(2ci−1)
因为题目说了,没有重边并且每条边只能参加一个环,直接DFS就行,甚至比上面还简单。
#pragma GCC optimize(2)
#include
#include
#include
#include
#include
using namespace std;
#define LL long long
const int N=3e5+5;
const int mod=998244353;
const int M=5e5+7;
int in[N];
int ha[M],len,sum,si;
struct node{
int v,next;
bool flag;
}E[2*M];
int head[N],cnt;
int fib[M];
inline int read(int &x){
char ch;
while((ch=getchar())>'9'||ch<'0');
x=ch-'0';
while((ch=getchar())>='0'&&ch<='9')
x=(x<<3)+(x<<1)+ch-'0';
}
inline void add(int u,int v){
E[cnt]={
v,head[u],0};
head[u]=cnt++;
}
void dfs(int u,int d){
// printf("%d %d\n",u,d);
in[u]=d; //记录这个点的进入时间
for(int i=head[u];~i;i=E[i].next)
if(!E[i].flag){
sum++;//边的数量
int v=E[i].v;
E[i].flag=1;
E[i^1].flag=1;
if(in[v]){
ha[++len]=in[u]-in[v]+1;
si+=in[u]-in[v]+1; //参与换的边的数量
}else{
dfs(v,d+1);
}
}
}
int main(){
fib[0]=1;
for(int i=1;i<M;i++){
fib[i]=fib[i-1]*2;
while(fib[i]>mod)
fib[i]-=mod;
}
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
len=0;cnt=0;
memset(head,-1,(n+1)*sizeof (int));
memset(in,0,(n+1)*sizeof (int));
int u,v;
for(int i=1;i<=m;i++){
read(u);read(v);
add(u,v);
add(v,u);
}
int x=0;
for(int i=1;i<=n;i++)
if(!in[i]){
sum=0;
si=0;
dfs(i,1);
x+=sum-si;
}
int ans=fib[x];
for(int i=1;i<=len;i++)
ans=1ll*ans*(fib[ha[i]]-1+mod)%mod;
printf("%d\n",ans);
}
return 0;
}
/**
5 5
1 1 4
1 2 3
2 2
1 2 7
2 1
*/