DRŽAVA题解
大概题意:把集合连在一起,要求有子集的人口数能被
考场上以为这道题是最小生成树,但看一眼数据范围我人就傻了(
于是我只考虑拿部分分 (写了个暴力最小生成树) 竟然有 (机房某巨拿了70pts,貌似我是判断能否被整除的时候出错了?)
来个码吧(大雾)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<algorithm>
#define ll long long
#define inf 1100000000
using namespace std;
int n,k,fa[51000],tot,sum[51000];
bool pd,ccf[51000][30];
struct node{
double x,y;
}no[51000];
struct edg{
int u,v;
double w;
}e[1100000];
void add(int u,int v){
e[++tot].u=u;
e[tot].v=v;
double x=no[u].x-no[v].x;
double y=no[u].y-no[v].y;
e[tot].w=sqrt(x*x+y*y);
}
bool cmp(edg a,edg b){
return a.w<b.w;
}
int findfa(int x){
return x==fa[x]?x:fa[x]=findfa(fa[x]);
}
int main(){
// freopen("drzava.in","r",stdin);
// freopen("drzava.out","w",stdout);
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
int rk=0;
scanf("%lf %lf %d",&no[i].x,&no[i].y,&rk);
sum[i]=rk;
if(rk%k==0)pd=1;
else ccf[i][rk%k]=1;
fa[i]=i;
}
if(pd==1){
puts("0.000");
return 0;
}
if(n<=1010){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(j==i)continue;
add(i,j);
}
}
sort(e+1,e+tot+1,cmp);
for(int i=1;i<=tot;i++){
int u=e[i].u;
int v=e[i].v;
int x=findfa(u);
int y=findfa(v);
if(x!=y){
fa[y]=x;
//printf("%d %d\n",x,y);
for(int j=1;j<k;j++){
if(ccf[x][j]&&ccf[y][k-j]){//这样子判断貌似是错的
printf("%.3lf",e[i].w);
return 0;
}
ccf[x][j]|=ccf[y][j];
//printf("%d ",ccf[x][j]);
}
//printf("\n");
sum[x]+=sum[y];
if(sum[x]%k==0){
printf("%.3lf",e[i].w);
return 0;
}
}
}
}
return 0;
}
然而另一位机房某巨用一种很巧妙的方法直接 (貌似会被(,而整除 (然而我证明不来),那么我们往前后50条边就好了。然而我用这种方法只能氵到 (为什么大佬能建下前后100条边而我只能建前后35条,建多了就会炸,就因为我是小丑?)
后附代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<algorithm>
#define ll long long
#define inf 1100000000
using namespace std;
int n,k,fa[51000],tot;
bool pd,mid[31];
struct node{
double x,y;
int rk;
bool ccf[31];
}no[51000];
struct edg{
int u,v;
double w;
}e[1100000*5];
void add(int u,int v){
e[++tot].u=u;
e[tot].v=v;
double x=no[u].x-no[v].x;
double y=no[u].y-no[v].y;
e[tot].w=sqrt(x*x+y*y);
}
bool cmp(edg a,edg b){
return a.w<b.w;
}
bool cnmp(node a,node b){
return (a.x*a.x+a.y*a.y)<(b.x*b.x+b.y*b.y);
}
int findfa(int x){
return x==fa[x]?x:fa[x]=findfa(fa[x]);
}
int main(){
// freopen("drzava.in","r",stdin);
// freopen("drzava.out","w",stdout);
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
int rk=0;
scanf("%lf %lf %d",&no[i].x,&no[i].y,&no[i].rk);
if(no[i].rk%k==0)pd=1;
else no[i].ccf[no[i].rk%k]=1;
fa[i]=i;
}
if(pd==1){
puts("0.000");
return 0;
}
sort(no+1,no+n+1,cnmp);
for(int i=1;i<=n;i++){
for(int j=max(1,i-35);j<=min(i+35,n);j++){//小丑双向边
add(i,j);
}
}
sort(e+1,e+tot+1,cmp);
for(int i=1;i<=tot;i++){
int u=e[i].u;
int v=e[i].v;
int x=findfa(u);
int y=findfa(v);
if(x!=y){
fa[y]=x;
memset(mid,0,sizeof mid);
for(int j=0;j<k;j++){
if(!no[x].ccf[j])continue;
for(int p=0;p<k;p++){//正确的判断方法
if(!no[y].ccf[p])continue;
mid[(p+j)%k]=1;
}
}
for(int j=0;j<k;j++){
no[x].ccf[j]|=mid[j],no[x].ccf[j]|no[y].ccf[j];
}
if(no[x].ccf[0]){
printf("%.3lf",e[i].w);
return 0;
}
}
}
return 0;
}
后来才发现我是小丑,我最小生成树建了双向边。 其实数据很水,只需要往后面建,然后就
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<algorithm>
#define ll long long
#define inf 1100000000
using namespace std;
int n,k,fa[51000],tot;
bool pd,mid[31];
struct node{
double x,y;
int rk;
bool ccf[31];
}no[51000];
struct edg{
int u,v;
double w;
}e[1100000*5];
void add(int u,int v){
e[++tot].u=u;
e[tot].v=v;
double x=no[u].x-no[v].x;
double y=no[u].y-no[v].y;
e[tot].w=sqrt(x*x+y*y);
}
bool cmp(edg a,edg b){
return a.w<b.w;
}
bool cnmp(node a,node b){
return (a.x*a.x+a.y*a.y)<(b.x*b.x+b.y*b.y);
}
int findfa(int x){
return x==fa[x]?x:fa[x]=findfa(fa[x]);
}
int main(){
// freopen("drzava.in","r",stdin);
// freopen("drzava.out","w",stdout);
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
int rk=0;
scanf("%lf %lf %d",&no[i].x,&no[i].y,&no[i].rk);
if(no[i].rk%k==0)pd=1;
else no[i].ccf[no[i].rk%k]=1;
fa[i]=i;
}
if(pd==1){
puts("0.000");
return 0;
}
sort(no+1,no+n+1,cnmp);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=min(i+50,n);j++){
add(i,j);
}
}
sort(e+1,e+tot+1,cmp);
for(int i=1;i<=tot;i++){
int u=e[i].u;
int v=e[i].v;
int x=findfa(u);
int y=findfa(v);
if(x!=y){
fa[y]=x;
memset(mid,0,sizeof mid);
for(int j=0;j<k;j++){
if(!no[x].ccf[j])continue;
for(int p=0;p<k;p++){
if(!no[y].ccf[p])continue;
mid[(p+j)%k]=1;
}
}
for(int j=0;j<k;j++){
no[x].ccf[j]|=mid[j],no[x].ccf[j]|no[y].ccf[j];
}
if(no[x].ccf[0]){
printf("%.3lf",e[i].w);
return 0;
}
}
}
return 0;
}
最后提一嘴正解,其实物理建边是不需要的,我们不妨换一种思路,将所有的点按
附上正解!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<algorithm>
#define ll long long
#define inf 1100000000
using namespace std;
int n,k,fa[51000],tot;
bool pd,mid[31];
struct node{
double x,y;
int rk;
bool ccf[31];
}no[51000];
double dist(int u,int v){
double x=no[u].x-no[v].x;
double y=no[u].y-no[v].y;
return sqrt(x*x+y*y);
}
bool cnmp(node a,node b){
return a.x==b.x?a.y<b.y:a.x<b.x;
}
int findfa(int x){
return x==fa[x]?x:fa[x]=findfa(fa[x]);
}
bool slove(double a){
for(int i=1;i<=n;i++){
memset(no[i].ccf,0,sizeof no[i].ccf);
no[i].ccf[no[i].rk]=1;
fa[i]=i;
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(no[j].x-no[i].x>a)break;
if(dist(i,j)>a)continue;
int u=i;
int v=j;
int x=findfa(u);
int y=findfa(v);
if(x!=y){
fa[y]=x;
memset(mid,0,sizeof mid);
for(int q=0;q<k;q++){
if(!no[x].ccf[q])continue;
for(int p=0;p<k;p++){
if(!no[y].ccf[p])continue;
mid[(p+q)%k]=1;
}
}
for(int q=0;q<k;q++){
no[x].ccf[q]|=mid[q],no[x].ccf[q]|=no[y].ccf[q];
}
if(no[x].ccf[0]){
return 1;
}
}
}
}
return 0;
}
int main(){
freopen("drzava.in","r",stdin);
// freopen("drzava.out","w",stdout);
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
int rk=0;
scanf("%lf %lf %d",&no[i].x,&no[i].y,&no[i].rk);
if(no[i].rk%k==0)pd=1;
no[i].rk%=k;
}
if(pd==1){
puts("0.000");
return 0;
}
sort(no+1,no+n+1,cnmp);
double l=0,r=1e7*5,mid;
while(l<=r-0.00005){
mid=(l+r)/2;
//printf("%lf\n",mid);
if(slove(mid)){
r=mid;
}
else{
l=mid;
}
}
printf("%.3lf",l);
return 0;
}