【题解】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;
}