9.4正睿CSP七连测day2
辛亏我没考,不然铁定掉 rating。
T1
T1 比较水,但是坑点比较多。挂了两次,第一次是没有处理前导 \(0\),第二次是没有处理后缀非数字字符。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N number
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
string s;
bool op=1;
ll a[5],tail;
int main(){
cin>>s;int len=s.length();
for(int i=0,j;i<=len-1;){
if(s[i]<'0'||s[i]>'9'){
if(tail==4) op=0;
if(s[i]!='.') op=0;
if(i>0&&(s[i-1]<'0'||s[i-1]>'9')) op=0;
i++;
continue;
}
tail++;
bool pd=1;
for(j=i;j<=len-1;j++){
if(s[j]=='0'&&pd) op=0;
if(s[j]!='0') pd=0;
if(s[j]>='0'&&s[j]<='9') a[tail]=1ll*a[tail]*10+s[j]-'0';
else break;
}
i=j;
}
for(int i=1;i<=tail;i++) if(a[i]>255){a[i]=255;op=0;}
if(op){printf("YES\n");return 0;}
printf("NO\n");
for(int i=1;i<=tail;i++){
printf("%lld",a[i]);if(i!=tail) putchar('.');
}
return 0;
}
T2
也比较水,只需要看前导 P 和 A,然后记录一下前导 P 的数量看能不能消即可。挂了一次,挂在前导 P 不应该清零。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 10010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int cnt=0,ans=0,tot;
char s[N];
int main(){
scanf("%s",s);int len=strlen(s);
for(int i=0;i<=len-1;i++){
if(s[i]=='A'){cnt++;}
if(s[i]=='P'){
if(cnt){cnt--;ans++;}
else{
tot++;
if(tot==2){tot=0;ans++;}
}
}
}
printf("%d\n",len-ans*2);
}
T3
T3 主要难度在于菱形的判断,稍加分析之后不难看出,我们记 \(bit\) 为若干 bitset,其中如果有一位为 \(1\) 表示两个点他们不互相派生且存在一个点可以派生出这两个点。
那么判断可不可以的时候我们只需要枚举直接继承点,构造一个 bitset,全部交一遍,看是否有交集就可以判断出是否可行。
我们考虑如何维护 \(bit\),我们在记一个 \(f\),同样也是一个 bitset,不过这个表示的是能够到达某个点的所有点所在集合,注意我们认为自身可以到达自身。显然我们可以把新加的点的 \(f\) 数组处理出来。然后我们枚举所有点,如果这个点到达不了当前点,并且他们的 \(f\) 值有交集,那么我们就相应更改 \(bit\)。复杂度为 \(O(\frac{n^3}{w})\)
不过这个题还可以优化到 \(O(n^2\log n)\) 这里简单的提一句。
我们假设当前的这个语句为 \(k:p_1,p_2,...p_k;\),我们把后面这些 \(p\) 按照声明顺序从大到小排序。显然只有声明顺序小的才能继承出声明顺序大的。 我们一次处理每一个 \(p\),维护一个 \(R\) 表示能到达 \(k\) 的节点集合。对于 \(p_i\),如果 \(R\) 里面这个位置已经为 \(1\),那么我们就不管,因为能够到达 \(p_i\) 也肯定已经在 \(R\) 中了,否则,我们把 \(R\) 与能够到达 \(p_i\) 的集合合并,如果有交,就说明一定出现了菱形。
我们看一下时间复杂度,这个 \(R\) 一共有 \(n\) 个位置,所以复杂度均摊下来是 \(O(n)\),一共做 \(n\) 次,所以复杂度为 \(O(n^2)\)。时间复杂度瓶颈在排序。如果用桶排,理论更优,但是常数较大,实际时间不如直接 sort。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M number
using namespace std;
// #define fre
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,tail=0;
map<string,int> Map;
bitset<1010> bit[N],f[N];
string s[N],ss;
int main(){
#ifdef fre
freopen("my.in","r",stdin);
freopen("my.out","w",stdout);
#endif
read(n);
for(int i=1;i<=n;i++){
cin>>ss;int j=0;cin>>s[j];
while(s[j][0]!=';') cin>>s[++j];j--;
bool op=1;
if(Map.find(ss)!=Map.end()){printf("greska\n");continue;}
for(int k=1;k<=j;k++) if(Map.find(s[k])==Map.end()){op=0;break;}
if(!op){printf("greska\n");continue;}
bitset<1010> sum;sum.reset();
for(int k=1;k<=j;k++) sum[Map[s[k]]]=1;
for(int k=1;k<=j;k++) if((sum&bit[Map[s[k]]]).any()){op=0;break;}
if(!op){printf("greska\n");continue;}
printf("ok\n");++tail;Map[ss]=tail;f[tail][tail]=1;
for(int k=1;k<=j;k++) f[tail]|=f[Map[s[k]]];
for(int k=1;k<=tail-1;k++){
if(f[tail][k]==0&&(f[k]&f[tail]).any()) bit[tail][k]=1;
}
}
}
T4
毒瘤题目。谢老板nb。
首先不难发现,\(k-degree\) 子图一定包含于 \((k-1)-degree\) 子图中,并且子图的数量本来就不多,这启示我们这道题应该是需要大力维护出这些子图的信息出来。
那么首先我们需要维护出每一个点最多能存在于那个子图中,即把 \(k\) 最大化的 \(k-degree\) 子图中。假设我们已经求出来了,并且存到了 \(vis\) 这个数组里面。我们下面称这个值为这个点的标记。
我们考虑从标记最大的点开始,一层一层扩张,每次需要维护集合合并,我们考虑用并查集维护。
那么这个题分两个步骤,我们首先来看第一个步骤怎么做。
我们首先桶排所有的点,按照他们的度数,然后我们从小到大枚举每一个点,考察每一条边,如果对点的当前度数大于你的度数,你就把它的度数减 \(1\),然后维护这个序列的有序性,维护方法就是维护值得分界线,这段代码如下:
inline void GetVis(){
for(int i=1;i<=n;i++) r[i].id=i,r[i].vis=D[i];
for(int i=1;i<=n;i++) c[r[i].vis]++;
for(int i=1;i<=n;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) Rank[i]=c[r[i].vis]--;
for(int i=1;i<=n;i++) a[Rank[i]]=r[i];
for(int i=1;i<=n-1;i++) if(a[i].vis!=a[i+1].vis) line[a[i].vis]=i;
for(int i=1;i<=n;i++){
for(int x=head[a[i].id];x;x=li[x].next){
int to=li[x].to;
if(a[Rank[to]].vis<=a[i].vis) continue;
a[Rank[to]].vis--;
int posi=line[a[Rank[to]].vis]+1;
line[a[Rank[to]].vis]++;
int id=a[posi].id;
swap(a[Rank[to]],a[Rank[id]]);
swap(Rank[to],Rank[id]);
}
}
for(int i=1;i<=n;i++) vis[a[i].id]=a[i].vis;
#ifdef debug
printf("here\nvis: ");Print(vis);
#endif
}
我们考虑正确性。我们先考虑一种显然正确的方法。我们令 \(k\) 为当前最小度数,然后我们删除全部度数小于等于 \(k\) 的点,需要及时修改一些点的度数,直到剩下的图全是度数大于 \(k\) 的点。这些被删除的点的标记都为 \(k\),我们考虑进一步增大 \(k\)。一直到整张图被删完。但是这种做法其实复杂度不对,原因就是我们可能会保留一些点较长时间,复杂度上界是根号的,但是到不了这个级别,但也绝对不是线性。我们考虑线性怎么做。
显然我们删除度数为 \(k\) 的点会改变一些点的度数,如果这些点原本的度数就已经小于等于 \(k\)(有可能被其他点删除导致小于 \(k\)),那么其实不管怎样这个点都将会在这一轮中删除。有影响的是那些大于 \(k\) 的度数,他们可能会被先删。我们一开始介绍的那种方法其实就是模拟了这一点,假设当前我们枚举的位置的度数为 \(k\),那么这个 \(k\) 一定是其标记,因为只有小于 \(k\) 的那些才能够更新他们。我们检查其每一条出边,我们可以让一些度数大的提早被删除。做完后剩下的度数就是标记。
有了标记之后我们怎么做?我们考虑把所有点按照标记排序,我们从大标记开始处理。考虑用并查集进行连通块之间的合并,我们记录每个连通块的点内度数之和,点数,边数之和。其中,前两个信息都是可以直接预处理合并的,我们考虑这个边数之和如何合并。
我们首先把所有点按照标记合并,我们枚举每个点,然后枚举每一条出边,如果对点在该点左侧,我们不管,这样可以保证每条边只被遍历一次,我们考虑该点与对点所在连通块,若不在同一个连通块,我们把连通块合并,边数当然也合并,然后我们把这条边的影响加上。
考虑正确性,其实是比较显然的,因为你每条边只被遍历一次,每次遍历会给在相应的连通块上进行贡献。
每次枚举到分界点后我们进行修改答案,看能不能更优,因为可能不连通,我们需要枚举所有点。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
// #define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 5000100
#define M number
using namespace std;
// #define fre
// #define debug
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Min(T a,T b){return a<b?a:b;}
template<typename T> inline T Max(T a,T b){return a<b?b:a;}
int ns[N],ms[N],ds[N],maxk,mink;
//最终答案。
ll ans=-INF,ansk;
int fa[N];
inline int Find(int x){
return x==fa[x]?x:Find(fa[x]);
}
struct edge{
int to,next;
inline void intt(int to_,int ne_){to=to_;next=ne_;}
}li[N<<1];
int head[N],tail,n,m,mm,nn,bb,d[N],D[N];
int vis[N];//打标记,有没有被删除,如果有,是在 k 等于几的时候被删除的。
int c[N],Rank[N],line[N],linetail;
struct rode{
int vis,id;
}r[N],a[N];
struct Graph{
//Init the graph
inline void Init(){
read(n);read(m);read(mm);read(nn);read(bb);
for(int i=1;i<=m;i++){int from,to;read(from);read(to);Add(from,to);Add(to,from);}
#ifdef debug
printf("here\n");
#endif
}
//add an edge
inline void Add(int from,int to){
li[++tail].intt(to,head[from]);
head[from]=tail;D[to]++;
}
//这个函数会求的 vis 数组,是每个位置的标记。
inline void GetVis(){
for(int i=1;i<=n;i++) r[i].id=i,r[i].vis=D[i];
for(int i=1;i<=n;i++) c[r[i].vis]++;
for(int i=1;i<=n;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) Rank[i]=c[r[i].vis]--;
for(int i=1;i<=n;i++) a[Rank[i]]=r[i];
for(int i=1;i<=n-1;i++) if(a[i].vis!=a[i+1].vis) line[a[i].vis]=i;
for(int i=1;i<=n;i++){
for(int x=head[a[i].id];x;x=li[x].next){
int to=li[x].to;
if(a[Rank[to]].vis<=a[i].vis) continue;
a[Rank[to]].vis--;
int posi=line[a[Rank[to]].vis]+1;
line[a[Rank[to]].vis]++;
int id=a[posi].id;
swap(a[Rank[to]],a[Rank[id]]);
swap(Rank[to],Rank[id]);
}
}
for(int i=1;i<=n;i++) vis[a[i].id]=a[i].vis;
#ifdef debug
printf("here\nvis: ");Print(vis);
#endif
}
inline void Print(int *a){
for(int i=1;i<=n;i++) printf("%lld ",a[i]);puts("");
}
inline void PreWork(){
fill(c+1,c+n+1,0);fill(Rank+1,Rank+n+1,0);
for(int i=1;i<=n;i++) c[vis[i]]++;
for(int i=1;i<=n;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) Rank[i]=c[vis[i]]--;
for(int i=1;i<=n;i++) c[Rank[i]]=i;
for(int i=1;i<=n;i++) ns[i]=1,ds[i]=D[i];
}
inline void Solve(){
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=n;i>=0;i--){
int now=c[i];
int fai=Find(now);
for(int x=head[now];x;x=li[x].next){
int to=li[x].to;
if(Rank[to]<Rank[now]) continue;
int fato=Find(to);
if(fai!=fato){
if(ns[fai]<=ns[fato]) swap(fato,fai);
fa[fato]=fai;ns[fai]+=ns[fato];ds[fai]+=ds[fato];ms[fai]+=ms[fato];
}
ms[fai]++;
}
if(vis[now]!=vis[c[i-1]]){
for(int j=i;j<=n&&vis[c[j]]==vis[now];j++){
int nowfa=Find(c[j]);
long long val=1ll*mm*ms[nowfa]-1ll*nn*ns[nowfa]+1ll*bb*(ds[nowfa]-(ms[nowfa]<<1));
if(val>ans){ans=val;ansk=vis[now];}
}
}
}
}
}g;
signed main(){
#ifdef fre
freopen("my.in","r",stdin);
freopen("my.out","w",stdout);
#endif
g.Init();g.GetVis();g.PreWork();g.Solve();
printf("%lld %lld\n",ansk,ans);
return 0;
}