【题解】2021.9.4 - zhengru IOI 七连测 Day2

T1 IP地址

思路

  • 真·按题意模拟

  • 不过有很多细节需要注意。

  • 首先,前导 \(0\) 可以删去仅当这个数里不是只有一个 \(0\) ,然后,一定要挨个扫描字符串,使用快读如果不复杂一点会漏读!

  • 然后没有了 \(TAT\)

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
bool GOOD=true;
string s;
int res,sez,oz;
int num[10],cnt;
bool in0;
inline bool csd(char chk){
	return (chk<='9'&&chk>='0');
}
int main(){
	cin>>s;
	int le=s.length();
	for(register int i=0;i<le;++i){
		if(s[i]>='0'&&s[i]<='9'){
			if(!in0&&s[i]=='0'&&csd(s[i+1])) GOOD=false;
			else in0=true;
			res=res*10+s[i]-'0'; 
			sez++;
			if(res>255){
				GOOD=false;
				res=255;
			}
		}
		if((csd(s[i-1])&&(!csd(s[i])))||((i==le-1)&&csd(s[i]))){
			num[++cnt]=res;
			sez=0;
			res=0;
			in0=false;
		}
		if((s[i]>'9'||s[i]<'0')&&(s[i]!='.'||cnt==4||cnt==0)){
			GOOD=false;
//			printf("NO AT 1 i=%d %c\n",i,s[i]);
		}
		if(!csd(s[i])) oz++;
	}
	if(GOOD&&oz==3) printf("YES\n");
	else{
		printf("NO\n");
		printf("%d.%d.%d.%d\n",num[1],num[2],num[3],num[4]);
	}
	return 0;
}

T2 字符串

思路

  • 由于 \(P\) 的作用似乎更大一些,所以我们可以考虑节约用 \(P\)

  • 我们注意到,若 \(P\)\(A\) 匹配,则一个 \(P\) 便可以对答案作出 \(-2\) 的贡献,而 \(P\)\(P\) 匹配则需要两个 \(P\)

  • 这就意味着,我们要尽可能多地将 \(P\)\(A\) 匹配。

  • 那么怎么匹配呢?

  • 显然, \(P\) 只能与其前面的 \(A\) 匹配,所以便有了以下几点:

    • 当某一位是 \(P\) 时,如果还有 \(A\) 未被匹配,则将 \(P\) 与该 \(A\) 匹配,如果没有,则在后面的位置也不可能再有 \(A\)\(P\) 匹配(因为顺序一定),可以将其扔入“废品堆”(即无法继续与 \(A\) 匹配的 \(P\) )。

    • 当某一位是 \(A\) 时,将 \(A\) 的数量加一,方便后面的 \(P\) 匹配。

  • 注意到我们只是简单粗暴地统计 \(A\) 的个数,那么,怎么保证 \(P\) 一定能匹配到前面的 \(A\) 呢?

  • 这并不难,由于原序列只有 \(A\)\(P\) 两种字符,那么在原序列中,当前的 \(P\) 与想与其匹配的 \(A\) 中间只会有 \(A\)\(P\) 两种字符。

  • 而它们中间能够匹配的 \(A\)\(P\) 都已经两两匹配,如果中间剩余了一个 \(P\) ,那么该 \(A\) 一定会优先与剩余的 \(P\) 匹配而不是当前的 \(P\) ,反之,如果中间剩余了若干个 \(A\) ,那么当前的 \(P\) 一定会优先与最靠后的 \(A\) 匹配。

  • 综上所述,当找到一个 \(P\) 并且还有 \(A\) 没有匹配时,该 \(P\) 一定能够成功匹配。

  • 统计完了以后,对于没有匹配 \(A\) 需要直接计入答案,而剩余的 \(P\) 则可以两两匹配,剩余的 \(P\) 与其奇偶性一致。

  • 根据以上思路,我们可以写出以下代码。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
int AA,PP;string s;
int main(){
	cin>>s;int le=s.length();
	for(register int i=0;i<le;++i) PP+=((AA==0)&&(s[i]=='P')),AA+=((s[i]=='P')?((AA==0)?0:-1):1);
	printf("%d\n",AA+(PP&1));
	return 0;
}

T3 继承类

思路

代码

T4 子图

思路

  • \(k - degree\) 子图的定义为,这个子图中的每个点都有 \(k\) 度,且与该图连通的其他点至多有 \(k-1\) 度。

  • \(CQQ\) 还定义了:

    • \(n(S)\) :该图中的顶点数。

    • \(m(S)\) :该图中的边数。

    • \(b(S)\) :该图中的点的所有边除去属于该图的边,一条边属于该图当且仅当这条边的两个端点都合法。

    • $score(S)=M \times m(S) - N \times n(S) + B \times b(S) $ :这就是该图的的分数辣。

  • 对了,注意亿下,在原图中有 \(k\) 度的点不一定在 \(\color{Red}k - degree\) 子图中!(因为与这个点相连的点可能因更加不合法被删除,这会导致这个点去世,所以在计算的时候,需要不断地判断、加点!)

  • 此外,对于每一个点 \(x\) ,我们统计一下 \(x\) 属于的 \(k-degree\) 子图中最大的那个 \(k\) ,记作 \(mx(x)\) ,并且用一个 \(vector\) 数组存下每一个 \(mx(x)=k\)\(x\) ,由于 \(k\) 不会很大,所以我们的空间不会很感人。

  • 考虑到 \(k-degree\) 一定是 \((k-1)-degree\) 的子图(要求更加严格),我们可以从最大的 \(k\) 开始枚举,不断向里加边,判断即可。

  • 对于每一个需要新加入的点 \(x\) (此时 \(x\)\(mx\) 恰好为 \(k\)),枚举以它为一个端点的所有边,记该边的终点为 \(y\) ,则如果 \(mx(y)>mx(x)\) ,这说明它是连向已经统计过答案的边,不会在枚举到 \(y\) 的时候重复统计,直接合并即可;相等则不然,需要对下标进行严格的大小判断,符合条件再合并;由于此时 \(x\)\(mx\) 恰好为 \(k\) ,如果 \(mx(y)<mx(x)\)\(y\) 一定不合法,直接舍弃即可。

  • 至于怎么合并,使用并查集即可。

  • 最后对于每个 \(x\) ,找到 \(x\) 的祖先,统计答案即可。

  • 但是,统计答案的时候, \(b(S)\) 似乎没法获得。

  • 不要紧,我们可以记录下每个点的初始度,将该初始度减去以 \(x\) 为一个端点的边数,剩下的即为 \(b(S)\)

  • 下面便是代码了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<utility>
#include<deque>
#include<ctime> 
#include<sstream>
#include<list>
#include<bitset> 
using namespace std; 
const int MAXN(1000233);
int n,m;//QWQ
int M,N,B;//参数 
//-----------------基本定义----------------- 
int cnt,head[MAXN],ded[MAXN];
struct edge{
	int to,nxt;
}ed[MAXN<<1];
inline void add(int fr,int to){
	ed[++cnt].to=to;
	ed[cnt].nxt=head[fr];
	head[fr]=cnt;
	ded[fr]++; 
	return;
}
//-----------------建图必备-----------------
int fa[MAXN];
int mx[MAXN];//所属的要求最严格的k-degree子图
int mmm[MAXN];//所属的连通块边数
int nnn[MAXN];//所属的连通块大小
int bbb[MAXN];//所属的连通块边界边数 
inline int fifa(int now){
	return now==fa[now]?now:fa[now]=fifa(fa[now]);
}//找fa
inline void mege(int x,int y){
	x=fifa(x),y=fifa(y);
	if(x!=y){
		fa[y]=x;//合并
		mmm[x]+=mmm[y]+1;//除了y有的边,这一条起连接作用的边也合法
		nnn[x]+=nnn[y];//大小合并
		bbb[x]+=bbb[y]-2;//同上,但是原来在两个图中都把这个边当边界边处理,需要减去 
	}//不在同一个块里
	else{
		mmm[x]++;//只是简单地加入了一条边,点数没有变化 
		bbb[x]-=2;//同上上 
	} 
}//加入一条边 
//-----------------并查集相关部分-----------------
queue<int> q;
vector<int> poi[MAXN];
int maxk,res,degree; 
long long ans=-1e18;
int main(){
	scanf("%d%d%d%d%d",&n,&m,&M,&N,&B);
	for(register int i=1;i<=m;++i){
		int xxx,yyy;
		scanf("%d%d",&xxx,&yyy);
		add(xxx,yyy);
		add(yyy,xxx);
	}
	for(register int i=1;i<=n;++i){
		fa[i]=i;
		nnn[i]=1;
		bbb[i]=ded[i];//假设全部为边界边,减去不是的即可 
	}
	int ready=0;
	for(register int kkk=0;kkk<n;++kkk){
		for(register int i=1;i<=n;++i){
			if(ded[i]==kkk) q.push(i);//只选等于kkk的是为了保证下面删点时判断的正确性 
		}
		while(q.size()){
			int xxx=q.front();
			q.pop();
			poi[kkk].push_back(xxx);//该删的删了,这个点的答案也就定了
			mx[xxx]=kkk;
			ready++;
			for(register int i=head[xxx];i;i=ed[i].nxt){
				int ttt=ed[i].to;
				if(--ded[ttt]==kkk) q.push(ttt);//这个点肯定撑不到下一个k,删掉这个点还会导致其他点撑不下去 
			}
		}
		if(ready==n){
			maxk=kkk;
			break;//全部算完了 
		}
	}
	for(register int kkk=maxk;kkk>=0;--kkk){
		int sss=poi[kkk].size();
		for(register int i=0;i<sss;++i){
			for(register int j=head[poi[kkk][i]];j;j=ed[j].nxt){
				int ttt=ed[j].to;
				if(kkk<mx[ttt]||((kkk==mx[ttt])&&(poi[kkk][i]<ttt))) mege(poi[kkk][i],ttt);
			}
		}
		for(register int i=0;i<sss;++i){
			int fff=fifa(poi[kkk][i]);
			res=M*mmm[fff]-N*nnn[fff]+B*bbb[fff];
			if(res>ans) ans=res,degree=kkk;
		}
	} 
	printf("%d %lld\n",degree,ans);
	return 0;
}
posted @ 2021-10-06 16:06  Binaries  阅读(58)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end