【題解】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\)
-
當某一位是 \(A\) 時,將 \(A\) 的數量加一,方便後面的 \(P\) 匹配。
-
-
注意到我們只是簡單粗暴地統計 \(A\) 的個數,那麼,怎麼保證 \(P\) 一定能匹配到前面的 \(A\) 呢?
-
這並不難,由於原序列只有 \(A\) 和 \(P\) 兩種字元,那麼在原序列中,當前的 \(P\) 與想與其匹配的 \(A\) 中間只會有 \(A\) 與 \(P\) 兩種字元。
-
而它們中間能夠匹配的 \(A\) 與 \(P\) 都已經兩兩匹配,如果中間剩餘了一個 \(P\)
-
綜上所述,當找到一個 \(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;
}