ABC401
AtCoder Beginner Contest 401
D - Logical Filling
不妨先考虑全是?的情况 . 若长度为偶数, 显然所有位置都应该是? .
奇数时 : 以5为例 , 若要有3个o , 则只能为o.o.o .
具体地 : 我们将原来的串转化一下 , 若与之相邻存在o,其一定是.,然后分割出一段一段的?,偶数长度不用管 , 记录一下最多能放多少个o , 以及需要放多少个o, 若恰好相等则每一个位置都固定了. 否则不用管. 特别地 ,若不用放o,需要将所有位置换成.
void solve() {
int n,k;cin>>n>>k;
string s;cin>>s;
s="&"+s;
vector<pair<int,int>> p;
for (int i=1;i<=n;++i) {
if (s[i]=='o') k--;
if (s[i]=='?') {
if (s[i-1]=='o') s[i]='.';
if (i+1<=n and s[i+1]=='o') s[i]='.';
}
}
int cnt=0;
for (int i=1;i<=n;++i) {
if (s[i]=='?') {
int r=i;
while (r<=n and s[r]=='?') r++;
r--;
cnt+=(r-i+2)/2;
if ((r-i+1)&1) {
p.push_back({i,r});
}
i=r;
}
}
if (cnt==k) {
for (auto [l,r]:p) {
for (int i=1;i<=r-l+1;++i) {
if (i&1) s[l-1+i]='o';
else s[l-1+i]='.';
}
}
}
if (k==0) {
for (int i=1;i<=n;++i) {
if (s[i]=='?') s[i]='.';
}
}
cout<<s.substr(1);
}
E - Reachable Set
First
先考虑简单的情况 , 如果只有一个点,需要删除多少个点呢?
我们的目标是删除该点能够到达的所有点. 用set存下即可. 如果有很多点也可以当成一个点,去维护set即可.
还有一个问题就是在加入\(x\)时,发现当前到不了,那就输出-1,并存下x,后面再补充.
具体实现见代码
vector<vector<int>> ver;
void solve() {
int n,m;cin>>n>>m;
ver.resize(n+1);
set<int> e;
for (int i=1;i<=m;++i) {
int x,y;cin>>x>>y;
if (x>y) swap(x,y);
if (x==1) e.insert(y);
ver[x].push_back(y);
ver[y].push_back(x);
}
cout<<e.size()<<"\n";
set<int> nd;
for (int i=2;i<=n;++i) {
if (e.find(i)!=e.end()) {
e.erase(i);
queue<int> q;
q.push(i);
while (q.size()) {
auto x=q.front();
q.pop();
for (auto y:ver[x]) {
if (nd.count(y)) q.push(y),nd.erase(y);
if (y>i) e.insert(y);
}
}
}else {
nd.insert(i);
}
if (nd.empty()) {
cout<<e.size()<<"\n";
}else {
cout<<"-1\n";
}
}
}
Second
上述过程可以通过并查集优化. 可以将nd转化为联通块的数量 , 我们将e用一个数组bad和一个计数器era代替.
具体地:用cnt计算当前联通块的数量 , bad记录当前应该删除的点 , era当前应该删除的点的数量.
每次加入一个点, 去遍历其边 , 若发现比它小的结点未联通 , 就将其联通 , 同时cnt--
若发现其连接了大于它的点,将其标记未坏点, 同时era++
若当前加入的点为一个坏点 那么era--.
具体见代码
void solve() {
int n,m;cin>>n>>m;
ver.resize(n+1);
for (int i=1;i<=n;++i) fa[i]=i;
for (int i=1;i<=m;++i) {
int x,y;cin>>x>>y;
if (x>y) swap(x,y);
ver[x].push_back(y);
ver[y].push_back(x);
}
vector<bool> bad(n+1);
int cnt=0,era=0;
for (int i=1;i<=n;++i) {
++cnt;
if (bad[i]) --era;
for (auto y:ver[i]) {
if (y<i) {
if (find(y)!=find(i)) {
merge(i,y);
cnt--;
}
}else {
if (!bad[y]) era++;
bad[y]=1;
}
}
if (cnt==1) {
cout<<era<<"\n";
}else {
cout<<"-1\n";
}
}
}
F - Add One Edge 3
本质是求树上所有点为根的最大深度
First
设\(a_i\)为在第一棵树中, 以\(i\)为根的最深深度 , 同理定义\(b_i\).
那么显然原问题就是求
其中mx为两棵树直径的较大者.
而第一步要先求出每个点的最深深度 , 用树形dp/换根dp可以求出. 然后求出直径 .
struct Tree {
vector<vector<int>> ver;
vector<int> dep,sec,a;
Tree(int n) {
ver.resize(n+1);
dep.resize(n+1);
sec.resize(n+1);
a.resize(n+1);
}
void addEdge(int x,int y) {
ver[x].push_back(y);
ver[y].push_back(x);
}
int getlen(int root) {
function<void(int, int)> dfs1 = [&](int x, int fa) -> void {
for (auto y : ver[x]) {
if (y == fa) continue;
dep[y] = dep[x] + 1;
dfs1(y, x);
}
if (dep[x] > dep[root]) {
root = x;
}
};
dfs1(root, 0);
int st = root;
int n=dep.size();
dep.assign(n,0);
dfs1(root, 0);
int ed = root;
return dep[root];
}
void dfs(int x,int fa) {
dep[x]=sec[x]=0;
for (auto y:ver[x]) {
if (y==fa) continue;
dfs(y,x);
int k=dep[y]+1;
if (dep[x]<k) {
sec[x]=dep[x];
dep[x]=k;
}else if (sec[x]<k) {
sec[x]=k;
}
}
}
void work() {
dfs(1,1);
function<void(int,int)> exroot=[&](int x,int fa) {
a[x]=dep[x];
for (auto y:ver[x]) {
if (y==fa) continue;
int lstx=dep[x],lsty=dep[y];
int lst2=sec[y];
if (dep[x]==dep[y]+1) dep[x]=sec[x];
if (dep[x]+1>dep[y]) {
sec[y]=dep[y];
dep[y]=dep[x]+1;
}else if (dep[x]+1>sec[y]) {
sec[y]=dep[x]+1;
}
exroot(y,x);
dep[x]=lstx;
dep[y]=lsty;
sec[y]=lst2;
}
};
exroot(1,1);
}
int queryk(int k) {
return a[k];
}
};
或者
struct Tree {
//.....
void dfs(int x,int fa) {
dep[x]=sec[x]=0;
for (auto y:ver[x]) {
if (y==fa) continue;
dfs(y,x);
int k=dep[y]+1;
if (dep[x]<k) {
sec[x]=dep[x];
dep[x]=k;
son[x]=y;
}else if (sec[x]<k) {
sec[x]=k;
}
}
}
void work() {
dfs(1,1);
function<void(int,int)> exroot=[&](int x,int fa) {
for (auto y:ver[x]) {
if (y==fa) continue;
up[y]=up[x]+1;
if (y==son[x]) up[y]=max(up[x],sec[x])+1;
else up[y]=max(up[x],dep[x])+1;
exroot(y,x);
}
};
exroot(1,1);
}
//....
};
然后我们可以利用二分,直接求出答案
sort(a.begin()+1,a.end());
sort(b.begin()+1,b.end());
for (int i=m;i;--i) s[i]=s[i+1]+b[i];
int ans=0;
for (int i = 1; i <= n; ++i){
int need = mx - a[i] - 1;
int x = lower_bound(b.begin()+ 1, b.end(), need) - b.begin();
ans += (x - 1) * mx;
ans += (m - x + 1) * (a[i] + 1) + s[x];
}
或者间接求出答案
int sum1=0,sum2=0;
for (int i=1;i<=n;++i) sum1+=a[i];
for (int i=1;i<=m;++i) sum2+=b[i];
int ans=n*m+n*sum2+m*sum1;
for (int i=1;i<=n;++i) cnt[a[i]]++;
for (int i=1;i<=n;++i) pre[i]=pre[i-1]+i*cnt[i];
for (int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
for (int i=1;i<=m;++i) {
int need=mx-b[i]-1;
need=min(need,n);
if (need<0) continue;
int x=cnt[need];
ans+=x*mx-(pre[need]+x*(b[i]+1));
}
这里need=min(need,n);比较坑. 会wa3个点.
Second
但是我们注意到 ,假设st->en为一条直径, 那么最大深度为\(\max(d(i,st),d(i,en))\) ,否则就能找到一条更长的路径.
于是在求直径时可以处理出这两个数组.
void dfs(int x,int fa) {
a[x]=max(a[x],dep[x]);
for (auto y: ver[x]) {
if (y==fa) continue;
dep[y]=dep[x]+1;
dfs(y,x);
}
}
int getlen(int root) {
function<void(int, int)> dfs1 = [&](int x, int fa) -> void {
for (auto y : ver[x]) {
if (y == fa) continue;
dep[y] = dep[x] + 1;
dfs1(y, x);
}
if (dep[x] > dep[root]) {
root = x;
}
};
dfs1(root, 0);
int st = root;
dep[st]=0,dfs(st,st);
int n=dep.size();
dep.assign(n,0);
dfs1(root, 0);
int ed = root,d=dep[root];
dep[ed]=0,dfs(ed,ed);
return d;
}

浙公网安备 33010602011771号