十二月七日 今天又是爆零的一天因为根本没交
伤心地我说不出话来
๐·°(৹˃﹏˂৹)°·๐
T1、
1、万能遥控器(ipv6.cpp)
【题目描述】
Z企业有各种各样的机器人。为了控制所有机器人,操作员Tom造了一个“万能遥控器”。只要向遥控器输入一个机器人的IP地址,就能遥控这个机器人。可是机器人实在太多,以至于使用IPv4协议都不能够唯一地标记每一个机器人。Tom听说Ipv6协议拥有近无穷个地址,于是他就想用IPv6协议为机器人设定IP地址。
不过,完整的IPv6地址很长(有39个字符),手工输入很费时间,所以IPv6有一套简化的机制。
IPv6的地址定义如下:
IPv6的地址有128位,以16位为一分组,每个16位分组写成4个十六进制数,中间用冒号分割,称为冒号分十六进制格式。
例如:21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A 是一个完整IPv6地址。
IPv6地址中每个16位分组中的前导零位可以去除做简化表示,但每个分组必须至少保留一位数字。如上例中的地址,去除前导零位后可写成:
21DA:D3:0:2F3B:2AA:FF:FE28:9C5A
某些地址中可能包含很长的零序列,为进一步简化表示法,还可以将冒号十六进制格式中相邻分组的连续零位合并,用双冒号“::”表示。“::”符号在一个地址中只能出现一次,该符号也能用来压缩地址中前部和尾部相邻的连续零位。
注意:(一个单独的0分组不能使用“::”省略。
例如地址:
样例一:1080:0:0:0:8:800:200C:417A 可表示为压缩格式 1080::8:800:200C:417A
样例二:0:0:0:0:0:0:0:1 可表示为压缩格式 ::1
样例三:0:0:0:0:0:0:0:0 可表示为压缩格式 ::
现在,Tom给了你一些IPv6地址,请你编写程序,把它们转换成完整的地址。
给你N个字符串,判断其是否合法,并转为如0000:0000:0000:0000:0000:0000:0000:0000(八组)的形式
压缩方式:
1.省略每组的前置零(如果只有一个不省)
2.每个数字为十六进制
3.连续两个冒号表示省略一串0(只准出现一次)
【输入格式】
第一行为一个正整数N,表示将有N个IP地址。
接下来有n行,每行一个非空字符串,表示一个地址。输入只包含数字、大写字母、冒号(:)和换行符,输入每行不超过39个字符。
【输出格式】
输出一共N行,每行一个字符串。对每个输入的字符串,如果符合IPv6标准,则输出其对应的完整的IPv6地址,否则输出“INVALID”
【输入样例】
4
1080::8:800:200C:417A
::1
::
1234567890ABCDEF
【输出样例】
1080:0000:0000:0000:0008:0800:200C:417A
0000:0000:0000:0000:0000:0000:0000:0001
0000:0000:0000:0000:0000:0000:0000:0000
INVALID
【数据范围】
对100%的数据,保证:1≤N≤35
分析:模拟就完了但是这鬼东西太讨厌了
#include<bits/stdc++.h>
using namespace std;
char s[100],a[100];
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,p2=0,p3=0,p4=0,k=0,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;
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;
for(int i=1;i<=k;i++) a[i]=s[k-i+1];
if(p4) {printf("INVALID\n");continue;}
int q=0;
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++;
}
s[++q]=':';
p1=0;
if(a[i+1]==':')
{
while(p2<=7)
{
for(int j=1;j<=4;j++)
s[++q]='0';
s[++q]=':';
p2++;
}
q--;
p1=4;
}
}
}
for(int i=39;i>=1;i--) printf("%c",s[i]);
cout<<endl;
}
return 0;
}
T2\
3、星球大战(starwar.cpp)
【问题描述】
很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系。某
一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有
的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。但好景不长,很快帝国又重
新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。
由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。
现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝
国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通快的个
数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连
通块中)。
给你一个无向联通图,每次操作删去一个点,求连通块总数
【输入格式】
输入文件第一行包含两个整数,N(1≤N≤2M)和 M(1≤M≤200,000),分别表示星
球的数目和以太隧道的数目。星球用 0~N-1的整数编号。
接下来的 M 行,每行包括两个整数 X,Y,其中 X,Y∈[0,N) 且 X≠Y ,表示星球 X
和星球 Y 之间有以太隧道。注意所有的以太隧道都是双向的。
之后输入一个整数 K(K≤N),表示帝国计划打击的星球个数。
接下来 K 行,每行一个星球编号 Di(0≤Di<N),表示打击的星球编号顺序。
【输出格式】
输出文件的第一行是开始时星球的连通块个数。接下来的K行,每行一个整数,表示经
过该次打击后现存星球的连通块个数。
【输入样例】starwar.in
8 13
0 1
1 6
6 5
5 0
0 6
1 2
2 3
3 4
4 5
7 1
7 2
7 6
3 6
5
1
6
3
5
7
【输出样例】starwar.out
1
1
1
2
3
3
【数据范围】
对于20%的数据,M≤10;
对于另20%的数据,N≤10000;
对于100%的数据,M ≤200000
这道题我们可以反过来做,怎么说呢,你可以先从最后最少的点开始(因为我们并不会删点),往前做,再倒着输出,就行了
#include <bits/stdc++.h>
using namespace std;
const int X = 400005;
int father[X],a[X],ansout[X],first[X],tol,tot,n,m;
bool use[X];
struct node{
int x,y,next;
}edge[X*2];
void add(int x,int y) //建立邻接表(x、y连成的边正反建了2次)
{
edge[++tol].x = x;
edge[tol].y = y;
edge[tol].next = first[x];
first[x] = tol;
}
int find_set(int x) //并查集找祖先
{
if(x!=father[x])
father[x] = find_set(father[x]);
return father[x];
}
void init()
{
memset(first,0,sizeof(first));
tol = 0;
for(int i=1;i<=n;i++)
{
use[i] = true;
father[i] = i; //并查集初始化,每个星球都是一个独立集合
}
int x,y;
for(int i=1;i<=m;i++) //读入m条边并建立邻接表
{
scanf("%d%d",&x,&y);
x++; y++; //星球的编号从1开始
add(x,y); //建边,邻接表
add(y,x); //反向建边
}
cin >> tot;
for(int i=1;i<=tot;i++)
{
scanf("%d",&a[i]); //读入要摧毁的星球
a[i]++; //星球的编号从1开始
use[a[i]]=false; //要摧毁的标记
}
}
void solve() //统计摧毁K个星球后的联通快个数
{
int x,y,px,py; //点x、y连成的边正反建了2次,并且编号相邻
for(int i=1;i<=tol;i+=2) //枚举所有边,通过并查集,合并边的两个端点,形成一个联通块
{
x = edge[i].x;
y = edge[i].y;
if(use[x]&&use[y]) //都不是要摧毁的点,建立并查集
{
px = find_set(x);
py = find_set(y);
father[px] = py;
}
}
int ans = 0;
for(int i=1;i<=n;i++) //统计全部摧毁后的连通块的数量
if(use[i]&&father[i]==i) ans++;
for(int i=tot;i;i--) //倒着枚举加入摧毁的点 x=a[i]
{
ansout[i] = ans;
int x = a[i];
use[x] = 1;
ans ++; //增加一个点,则假设增加一个联通快
for(int j=first[x];j;j=edge[j].next) //枚举从x出发的每一条边
{
int y = edge[j].y; //y为x出发的边对面端点
if(use[y])
{
py = find_set(y);
px = find_set(x);
if(px!=py) //祖先不一致,则合并
{
father[px] = py;
ans --; //连通块减一
}
}
}
}
ansout[0] = ans; //没有摧毁任何一个星球的答案
for(int i=0;i<=tot;i++)
cout<<ansout[i]<<endl; //输出依次催毁的相应星球
}
int main()
{
freopen("starwar.in","r",stdin);
freopen("starwar.out","w",stdout);
cin >> n >> m;
init();
solve();
return 0;
}
T3\
- 次小生成树(tree.cpp/c/pas)
【问题描述】
小 C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正
当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的。
这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。
【输入格式】
第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y
z 表示,点 x 和点y之间有一条边,边的权值为z。
【输出格式】
包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生
成树)
【输入样例】tree.in
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
【输出样例】tree.out
11
【数据范围】
数据中无向图无自环;
50% 的数据N≤2000 M≤3000;
80% 的数据N≤50000 M≤100000;
100% 的数据N≤100000;M≤300000,
边权值非负且不超过 10
以前我们曾经学过求最小生成树,可以再此基础上进行次小生成树的探究,通过思考我们不难发现:假如说我们找到了最小生成树,删除它的某一条边,再连上另一条边,对每条边进行,若这两边差值最小,则树的总权值即为次小生成树的总权值
#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; p[1]=1; //d是深度;点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()
{
ans=INF;
init();
MST();
bfs();
for (int i=1;i<=m;i++)
if (!b[i]) find(i);
cout<<ans<<endl;
return 0;
}