191207Practice

第一次爆零
我真是大起大落(详见)

逻辑啊,逻辑啊
信息学就是数学+英语+逻辑学

T1 IPv6

超级日隆
细节,条件限制很多
如下:

  1. 两个:之间最多有4个字符
  2. 最多有两个::省略
  3. :个数不多于7个
  4. 如果有7个:,且存在连续的,则其位置一定在最前或最后
  5. 16进制!虽然数据都合法

因此,用到方法:

int p1=0/验算两个:之间有没有超过4个字符/;
int p2=0/储存:的个数/;
int p3=0/储存连续两个:的个数/;
int p4=0/如果存在不合法,p4记录/;
int k=0/指针/;
int point=0/储存两个连续:中后一个的位置/;

我对zgs的代码的批注(有理有据地让你们知道什么叫丑)

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

char s[100],a[100];//s存简化码 
int n;

int main()
{
//	freopen("ipv6.in","r",stdin);
//	freopen("ipv6.out","w",stdout);
	scanf("%d\n",&n);
	for(int ii=1;ii<=n;ii++)
	{
		memset(s,'0',sizeof(s));
		memset(a,'0',sizeof(a));
		char c;
		int p1=0/*验算两个:之间有没有超过4个字符*/;
		int p2=0/*储存:的个数*/;
		int p3=0/*储存连续两个:的个数*/;
		int p4=0/*如果存在不合法,p4记录*/;
		int k=0/*指针*/;
		int point=0/*储存两个连续:中后一个的位置*/;
		while((c=getchar())!=EOF && c!='\n')
		{
			s[++k]=c;//读入一个处理一个 
			if(c==':') {p1=0; p2++;}
			else p1++;//记录:的个数 
			if(p1>4 || p2>7) p4=1;//:之间超4个,舍;:个数多于7个,舍; 
			if(c==':' && s[k-1]==':') {p3++;point=k;}//记录连续的:组数与后一个:的坐标 
			if(p3>1) p4=1;//超过一组,舍; 
		}
		if(!p3 && p2<7) p4=1;//没有省略但是:个数少了,舍 
		if(p3 && p2==7 && point!=2 && point!=k) p4=1;
		//连续:的位置不对(如果有7个:,且存在连续的,则其位置一定在最前或最后) 
		for(int i=1;i<=k;i++) a[i]=s[k-i+1];		   	   //倒序存入a ,目的是处理前导0 
	    if(p4) {printf("INVALID\n");continue;}	    //p4记录了不合法的情况,若不合法,舍 
	    
		int q=0;//q是s的指针,注意,这里的s不是原来的s 
		p1=0;
	    for(int i=1;i<=k;i++)
	    {
			s[++q]=a[i];
			p1++;
			if(a[i]==':')
			{
				q--;
				while(p1<5)
				{
					s[++q]='0';
					p1++;//凑够4位 
				}
				s[++q]=':';
				p1=0;//四位处理完,打:结尾 
				if(a[i+1]==':')//省略全0的情况 
				{
					while(p2<=7)//补齐省略的全0 
					{
						for(int j=1;j<=4;j++)
						  s[++q]='0';
						s[++q]=':';
						p2++;
					}
					q--;//基于i发现i+1与i是省略的:,要退格 
					p1=4;
				}
			}
	   }	      
		for(int i=39;i>=1;i--) printf("%c",s[i]);//倒序输出答案 
		cout<<endl;
	}
	return 0;
}

T2 Starwar

这不就是暑假做过的那道题吗??!T_T
并查集,倒着加点(一会儿把梁老讲课的内容消化一下,今天zgs让他给我们讲了带权并查集,我似乎又回到了暑假的听天书状态T_T)

模块化让你的代码更清晰
找到连通块的祖先: f a t h e r i = i father_i=i
枚举边优化:i+=2
加点增减连通块的数量:不需要再求一遍,如果有合并,连通块数量-1(注意,添加一个点之后连通块数量自然要+1)

注意这道日隆题星球的编号从0开始,+1变成习惯的

星期六还T3个,现在T2个
最后发现zgs的网站里面printf比cout慢。。。
damn it浪费我时间
这个代码是ok的↓↓↓↓↓↓

#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define re register
const int NNN=(int)1e5+10;
int n,m,k;
int tot,first[NNN];
struct bian{
	int ori,aim,nxt;
}edge[NNN<<2];
int a[NNN>>1];//摧毁顺序 
int father[NNN];
bool exist[NNN];//存在的星球 
int ans,ansout[NNN>>1];

inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

inline void Init(){
	for(re int i=1;i<=n;i++)father[i]=i,exist[i]=true;
	ans=0;
}

inline void Add(int u,int v){
	tot++;
	edge[tot].nxt=first[u];
	first[u]=tot;
	edge[tot].aim=v;
	edge[tot].ori=u;
}

inline void GetData(){
	n=in,m=in;
	Init();
	for(re int i=1;i<=m;i++){
		int u=in+1,v=in+1;
		Add(u,v),Add(v,u);
	}
	k=in;
	for(re int i=1;i<=k;i++)a[i]=in+1,exist[a[i]]=false;
}

inline int getfather(int i){
	if(father[i]==i)return father[i];
	father[i]=getfather(father[i]);
	return father[i];
}

inline void Solve(){
	for(re int i=1;i<=tot;i+=2){
		int u=edge[i].aim,v=edge[i].ori;
		if(exist[u]&&exist[v]){
			u=getfather(u),v=getfather(v);
			father[u]=v;
		}
	}
	
	for(re int i=1;i<=n;i++)
		if(exist[i]&&father[i]==i)
			ans++;
	
	for(re int i=k;i;i--){
		
		ansout[i]=ans;
		exist[a[i]]=true;
		ans++;
		
		for(re int e=first[a[i]];e;e=edge[e].nxt){
			int u=getfather(edge[e].ori),v=edge[e].aim;
			if(exist[v])
				if(u!=getfather(v))
					father[u]=getfather(v),ans--;
		}
	}
	ansout[0]=ans;
	
	for(re int i=0;i<=k;i++)printf("%d\n",ansout[i]);
}

int main(){
	GetData();
	Solve();
	return 0;
}

T3 tree

转化思想
先整一个最小生成树,在通过一点操作得到次小生成树

MST(minimum spanning tree)
经典的Kruscal,并查集实现(还可以用Prim,可惜我不会)

运用到贪心思想
先讲一下次短路:(当然NOI选手还可以炫一炫A*)
最短路上的边依次删去,再求最短路
得到的路径中最小的就是次短路长度

再提醒一下:求路径上的点的方法——用栈(已知起点和终点)

类似于次短路
得到MST之后,从不在MST里的边中选一条,称为u
那么这条边一定会与MST构成一个环
于是删去该环上最大且不为该边的边,称为v
于是, Δ w e i = v u \Delta wei=v-u
依次枚举不在MST里的边,得到 Δ w e i m i n \Delta wei_{min}
那么次小生成树的权值总和就 + Δ w e i m i n +\Delta wei_{min}

我水平有限,复制一个标程在下面
但是他用的是树剖在做

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

const LL INF=(LL)1e12;
const int N=(int)1e5 + 20;
const int M=3*(int)1e5 + 20;
int n,m,size,father[N],first[N];
int d[N],g[N][20],maxx[N][20][2];
int mx[2],p[N*3];
bool b[M],v[M];
LL ans,mst;

struct node
{
	int a;
	int b;
	int c;
}e[M];

struct node1
{
	int to;
	int len;
	int next;
}bian[M*2];

void inser(int x,int y,int w)
{
	size++;
	bian[size].next=first[x];
	first[x]=size;
	bian[size].to=y;
	bian[size].len=w;
}

void init()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
	    scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
}

bool cmp(const node &a,const node &b)
{
	return a.c<b.c;
}

int search(int v)
{
	if (father[v]==v)
	    return v;
	father[v]=search(father[v]);
	return father[v];
}

void MST()                     //krusakal算法求最小生成树  
{
	for (int i=1;i<=n;i++)
	    father[i]=i;
	int t=0;
	sort(e+1,e+m+1,cmp);       //按边的长度排序 
	for (int i=1;i<=m;i++)     //从小到大枚举每条边 
	{
		int x=search(e[i].a);
		int y=search(e[i].b);
		if (x!=y)              //第i条边两端不在一个集合 
		{
			father[x]=y;       //合并 
			mst+=e[i].c;       //最小生成树的权值 
			t++;
			b[i]=true;         //标记i边已经放进最小生成树 
			inser(e[i].a,e[i].b,e[i].c);   //把i边建立邻接表 
	    	inser(e[i].b,e[i].a,e[i].c);   //反向建立邻接表 
		}
		if (t==n-1) break;        //如果加进了n-1条边,则最小生成树加边完成 
	}
}

void up_max(int &a,int &b,int w)  //更新 
{
	if (w>a)
		{b=a;a=w;return;}
	if (w>b&&w!=a)
	    {b=w;return;}
}

void bfs()
{
	memset(g,-1,sizeof(g));
	int head=0,tail=1;
	d[1]=1;                    //d是深度
	p[1]=1;                    //点1入队列 
	v[1]=true;
	while (head<tail)
	{
		int x=p[++head];
		for (int u=first[x];u;u=bian[u].next)  //枚举x出发的每条边 
			if (!v[bian[u].to])                //边指向的点不在队列中            
			{
				int y=bian[u].to;
				d[y]=d[x]+1;                   
				v[y]=true;
				g[y][0]=x;
				maxx[y][0][1]=bian[u].len;
				int k=0;
				while (g[y][k]!=-1)  //倍增 maxx[y][k] 表示这段路径上的最长边和次长边 
				{
					g[y][k+1]=g[g[y][k]][k];
					up_max(maxx[y][k+1][1],maxx[y][k+1][0],maxx[y][k][0]);
					up_max(maxx[y][k+1][1],maxx[y][k+1][0],maxx[y][k][1]);
					up_max(maxx[y][k+1][1],maxx[y][k+1][0],maxx[g[y][k]][k][1]);
					up_max(maxx[y][k+1][1],maxx[y][k+1][0],maxx[g[y][k]][k][0]);
					k++;
				}
				p[++tail]=y;
			}
	}
}

int jump(int x,int l)
{
	for (int i=19;i>=0;i--)
	    if ((1<<i)<=l)
	    {
	    	l-=1<<i;
			up_max(mx[1],mx[0],maxx[x][i][0]);
			up_max(mx[1],mx[0],maxx[x][i][1]);
	    	x=g[x][i];
	    }
	return x;
}

void find(int v)   //最小生成树和次小生成树之间只有一边之差,枚举去掉哪条边,新连接一条边会构成一个环,环上找最值 
{
	int x=e[v].a,y=e[v].b,w=e[v].c;
	mx[0]=mx[1]=0;
	if (d[x]<d[y])
	    swap(x,y);
	x=jump(x,d[x]-d[y]);
	if (x==y)
	{
		if (w!=mx[1])ans=min(ans,mst+w-mx[1]);
		    else ans=min(ans,mst+w-mx[0]);
	}
	for (int i=19;i>=0;i--)
        if (g[x][i]!=g[y][i])
        {
			up_max(mx[1],mx[0],maxx[x][i][0]);
			up_max(mx[1],mx[0],maxx[x][i][1]);
			up_max(mx[1],mx[0],maxx[y][i][0]);
			up_max(mx[1],mx[0],maxx[y][i][1]);
            x=g[x][i];
            y=g[y][i];
        }
    up_max(mx[1],mx[0],maxx[x][0][1]);
    up_max(mx[1],mx[0],maxx[y][0][1]);
    if (w!=mx[1])ans=min(ans,mst+w-mx[1]);
		else ans=min(ans,mst+w-mx[0]);
}

int main()
{
//	freopen("tree.in","r",stdin);
//	freopen("tree.out","w",stdout);
	ans=INF;
	init();
	MST();
	bfs();
	for (int i=1;i<=m;i++)
	    if (!b[i]) find(i);
	cout<<ans<<endl;	
	return 0;
}

发布了26 篇原创文章 · 获赞 3 · 访问量 901

猜你喜欢

转载自blog.csdn.net/Antimonysbguy/article/details/103433801