Educational Codeforces Round 82 (Rated for Div. 2)
A.Erasing Zeroes
找左右两端的 1,然后统计中间 0 的个数即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 106
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char str[104];
void solve() {
scanf("%s",str+1);
int n=strlen(str+1);
int mi=N, ma=0;
for(int i=1;i<=n;++i) {
if(str[i]=='1') {
mi=min(mi, i);
ma=max(ma, i);
}
}
if(mi==N) printf("0\n");
else {
int cnt=0;
for(int i=mi;i<=ma;++i) {
if(str[i]=='0') ++cnt;
}
printf("%d\n",cnt);
}
}
int main() {
// setIO("input");
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
B.National Project
显然要贪心先修好的路,然后前面修完整块,最后一个块只修好路.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
void solve() {
ll n,g,b;
scanf("%lld%lld%lld",&n,&g,&b);
ll need=(n+1)/2;
ll base=need/g, res=need%g;
ll ans=base*(g+b);
if(res==0) printf("%lld\n",max(n, ans-b));
else {
printf("%lld\n",max(n, ans+res));
}
}
int main() {
// setIO("input");
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
C.Perfect Keyboard
显然如果 $\mathrm{s}$ 串中字符 $\mathrm{c}$ 与超过两种字符相邻是不合法的.
而与 $1$ 种字符相邻的字符一定是答案串的开头.
随便选一个与 $1$ 种字符相邻的字符串,然后向右拓展即可,拓展方式显然是唯一的.
若未能拓展完所有出现过的字符则同样不合法.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 205
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int ch[27][27],vis[30],cnt[30];
char str[N];
vector<int>g[30],ans;
void solve() {
memset(ch, 0, sizeof(ch));
memset(vis, 0, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
for(int i=0;i<28;++i) g[i].clear();
ans.clear();
scanf("%s",str+1);
int n=strlen(str+1);
if(n==1) {
printf("YES\n");
printf("%c",str[1]);
for(int i=0;i<26;++i)
if(str[1]-'a' != i)
printf("%c",i+'a');
printf("\n");
}
else {
int flag=0;
for(int i=2;i<=n;++i) {
int a=str[i-1]-'a';
int b=str[i]-'a';
if(!ch[a][b]) cnt[a]++,g[a].pb(b),ch[a][b]=1;
if(!ch[b][a]) cnt[b]++,g[b].pb(a),ch[b][a]=1;
if(cnt[a]>2||cnt[b]>2) {
printf("NO\n");
return ;
}
}
if(!flag) {
int o=-1,num=0;
for(int i=0;i<26;++i) {
if(cnt[i]==1) o=i;
if(cnt[i]) ++num;
}
if(o==-1) printf("NO\n");
else {
int cur=o;
vis[o]=1;
ans.pb(o);
for(int i=2;i<=num;++i) {
for(int j=0;j<26;++j) {
if(!vis[j] && ch[cur][j]) {
ans.pb(j);
vis[j]=1;
cur=j;
break;
}
}
}
if(ans.size()==num) {
printf("YES\n");
for(int i=0;i<ans.size();++i)
printf("%c",ans[i]+'a');
for(int i=0;i<26;++i)
if(!cnt[i])
printf("%c",i+'a');
printf("\n");
}
else {
printf("NO\n");
}
}
}
}
}
int main() {
// setIO("input");
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
D.Fill The Bag
设 $\mathrm{sum}=\sum_{\mathrm{i=1}}^{\mathrm{m}} \mathrm{a[i]}$.
显然若 $n \leqslant sum$ 则一定合法,否则不合法.
先将 $\mathrm{m}$ 个数都选上,然后我们要减掉 $\mathrm{sum-n}$
有两种操作:
1. 将两个 $2^{\mathrm{i}}$ 形式的数字合并,无代价.
2. 将一个 $2^{\mathrm{i}}$ 形式的数字除以 2,代价为 $1$.
不妨从低位向高位考虑.
若前面的低位需要补一个 $2^{\mathrm{i}}$,则能给就给.
这样做正确是因为 $2^{\mathrm{i}}$ 做除法覆盖的是一个区间,所以与更靠前的匹配一定最优.
若当前位还剩一些数,则将这些数合并起来,可以贡献给更高位.
#include <cstdio>
#include <vector>
#include <set>
#include <map>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
ll n,a[N];
int m,cnt[70];
map<ll,int>mp;
void init() {
for(int i=0;i<=32;++i)
mp[1ll<<i]=i;
}
ll calc(ll x) {
// printf("%lld\n",x);
int lst=-1;
ll ans=0;
for(int i=0;i<=32;++i) {
if(cnt[i]&&lst!=-1) {
--cnt[i],ans+=i-lst;
lst=-1;
}
if((x >> i) & 1) {
// 当前需要.
if(cnt[i]) --cnt[i];
else if(lst==-1) lst=i;
}
cnt[i+1]+=(cnt[i]>>1);
// cnt[i]=cnt[i]&1;
}
return ans;
}
void solve() {
scanf("%lld%d",&n,&m);
memset(cnt, 0, sizeof(cnt));
for(int i=1;i<=m;++i) {
scanf("%lld",&a[i]);
cnt[mp[a[i]]]++;
}
ll sum=0;
for(int i=1;i<=m;++i) sum+=a[i];
if(n>sum) printf("-1\n");
else printf("%lld\n",calc(sum-n));
}
int main() {
// setIO("input");
int T;
init();
scanf("%d",&T);
while(T--) solve();
return 0;
}
E.Erase Subsequences
考虑枚举这个分界点 $\mathrm{p}$,也就是说 $|t|$ 中前 $p$ 个由第一个串构造,$\mathrm{p}$ 后由第二个串构造.
$\mathrm{f[i][j]}$ 表示考虑完 $s$ 串的前 $\mathrm{i}$ 个位置,匹配完第一个串前 $j$ 个位置的情况下第二个串的最大匹配长度.
总时间复杂度为 $O(n^3)$.
#include <cstdio>
#include <vector>
#include <set>
#include <map>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
ll n,a[N];
int m,cnt[70];
map<ll,int>mp;
void init() {
for(int i=0;i<=32;++i)
mp[1ll<<i]=i;
}
ll calc(ll x) {
// printf("%lld\n",x);
int lst=-1;
ll ans=0;
for(int i=0;i<=32;++i) {
if(cnt[i]&&lst!=-1) {
--cnt[i],ans+=i-lst;
lst=-1;
}
if((x >> i) & 1) {
// 当前需要.
if(cnt[i]) --cnt[i];
else if(lst==-1) lst=i;
}
cnt[i+1]+=(cnt[i]>>1);
// cnt[i]=cnt[i]&1;
}
return ans;
}
void solve() {
scanf("%lld%d",&n,&m);
memset(cnt, 0, sizeof(cnt));
for(int i=1;i<=m;++i) {
scanf("%lld",&a[i]);
cnt[mp[a[i]]]++;
}
ll sum=0;
for(int i=1;i<=m;++i) sum+=a[i];
if(n>sum) printf("-1\n");
else printf("%lld\n",calc(sum-n));
}
int main() {
// setIO("input");
int T;
init();
scanf("%d",&T);
while(T--) solve();
return 0;
}
F.Number of Components
连通块个数与连通性有关,不妨用并查集来维护连通性.
对于每种颜色,单独考虑.
将 $\mathrm{col[x][y]}$ 改为 $\mathrm{c}$ 可以看作是 $\mathrm{col[x][y]}$ 的断边与 $\mathrm{c}$ 的加边.
加边好处理,但是断边不好处理.
好在题目保证 $\mathrm{c[i]} \leqslant \mathrm{c[i+1]}$,故对于颜色 $c$ 来说,一定先有加边后有断边.
这意味着一旦颜色 $c$ 进行断边,则不会再碰到颜色 $c$ 的加边情况.
将断边操作按照时间倒叙枚举,变成加边,然后对于答案的增量取相反数即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 302
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int dx[]={-1, 0, 1, 0};
int dy[]={0, -1, 0, 1};
int n,m,Q,scc;
int p[N*N],a[N][N],id[N][N],is[N][N],ans[2000005];
struct data {
int x, y, ti;
data(int x=0,int y=0,int ti=0):x(x),y(y),ti(ti){}
};
vector<data>add[2000003],del[2000003];
void init() {
for(int i=1;i<=n*m;++i) p[i]=i;
}
int find(int x) {
return p[x]==x?x:p[x]=find(p[x]);
}
int merge(int x, int y) {
x=find(x);
y=find(y);
if(x==y) return 0;
p[x]=y;
return 1;
}
void work(const vector<data>&g, int d) {
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) is[i][j]=0;
init();
for(int i=0;i<g.size();++i) {
data e=g[i];
int x=e.x;
int y=e.y;
int t=e.ti;
int cur=1;
is[x][y]=1;
for(int j=0;j<4;++j) {
int xx=x+dx[j];
int yy=y+dy[j];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&is[xx][yy]) {
cur-=merge(id[xx][yy], id[x][y]);
}
}
ans[t]+=d*cur;
// 合并完毕.
}
}
int main() {
// setIO("input");
scanf("%d%d%d",&n,&m,&Q);
init();
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) id[i][j]=++scc;
int mx=1;
for(int i=1;i<=Q;++i) {
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
if(a[x][y]==c) continue;
del[a[x][y]].pb(data(x, y, i));
add[a[x][y]=c].pb(data(x, y, i));
mx=max(mx, c);
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) del[a[i][j]].pb(data(i, j, Q+1));
for(int i=0;i<=mx;++i) {
reverse(del[i].begin(), del[i].end());
}
for(int i=0;i<=mx;++i) {
work(add[i], +1);
work(del[i], -1);
}
int fin=1;
for(int i=1;i<=Q;++i) {
fin+=ans[i];
printf("%d\n",fin);
}
return 0;
}
G.Sum of Prefix Sums
路径问题考虑用点分治进行处理.
在处理分治中心的时候只伸向一个子树的路径是好处理的.
若路径要伸向两个子树,则固定一个子树,对于第二个子树来说深度+1,第一个子树的贡献是线性的.
那么就将第一个子树的贡献看作是一次函数,用李超线段树进行维护.
特别注意求前缀和的前缀和的最大值时要注意方向,所以要正反跑两次.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 170002
#define ll long long
#define pb push_back
#define ls (now<<1)
#define rs (now<<1|1)
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N];
ll ans,d1[N],d2[N],d3[N],d4[N],a[N];
int size[N],dep[N],f[N],vis[N],sn,n,root,cnt;
struct Line {
ll k,b;
Line() { k=b=0; }
}line[N];
ll calc(Line o, int pos) {
return 1ll*o.k*pos+o.b;
}
struct SGT {
ll ma[N<<2];
int tree[N<<2];
vector<int>clr;
void modify(int l,int r,int now,int x) {
int mid=(l+r)>>1;
clr.pb(now);
if(calc(line[x], mid) > calc(line[tree[now]], mid))
swap(tree[now], x);
if(l!=r&&calc(line[x], l) > calc(line[tree[now]], l))
modify(l,mid,ls,x);
if(l!=r&&calc(line[x], r) > calc(line[tree[now]], r))
modify(mid+1,r,rs,x);
if(l!=r) ma[now]=max(ma[now], max(ma[ls], ma[rs]));
ma[now]=max(calc(line[tree[now]], l), calc(line[tree[now]], r));
}
ll query(int l,int r,int now,int L,int R) {
if(l>=L&&r<=R) {
return ma[now];
}
int mid=(l+r)>>1;
ll re=max(calc(line[tree[now]], max(l,L)), calc(line[tree[now]], min(r,R)));
if(L<=mid) re=max(re, query(l,mid,ls,L,R));
if(R>mid) re=max(re, query(mid+1,r,rs,L,R));
return re;
}
void CLR() {
for(int i=0;i<clr.size();++i) {
tree[clr[i]]=ma[clr[i]]=0;
}
clr.clear();
}
}T;
void getroot(int x, int ff) {
size[x]=1,f[x]=0;
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(v==ff||vis[v]) continue;
getroot(v, x);
size[x]+=size[v];
f[x]=max(f[x], size[v]);
}
f[x]=max(f[x], sn-size[x]);
if(f[x]<f[root]) root=x;
}
void getans(int x, int ff) {
ans=max(ans, T.query(1, n, 1, dep[x], dep[x])+1ll*d1[x]*dep[x]+d2[x]);
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(vis[v]||v==ff) continue;
dep[v]=dep[x]+1;
d1[v]=d1[x]+a[v];
d2[v]=d2[x]+1ll*a[v]*(1-dep[v]);
getans(v, x);
}
}
void update(int x, int ff) {
++cnt;
line[cnt].b=d3[x];
line[cnt].k=(ll)d4[x];
T.modify(1,n,1,cnt);
ans=max(ans, d3[x]);
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(vis[v]||v==ff) continue;
d3[v]=d3[x]+1ll*a[v]*(dep[v]+1);
d4[v]=d4[x]+a[v];
update(v, x);
}
}
void dfs(int x) {
// 将 x 插入.
vis[x]=1;
cnt=1;
line[cnt].b=a[x];
line[cnt].k=a[x];
T.modify(1, n, 1, cnt);
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(vis[v]) continue;
dep[v]=1;
d1[v]=a[v];
d2[v]=0;
getans(v, x);
d3[v]=a[x]+2ll*a[v];
d4[v]=a[x]+a[v];
update(v, x);
}
T.CLR();
cnt=1;
line[cnt].b=a[x];
line[cnt].k=a[x];
T.modify(1,n,1,cnt);
for(int i=G[x].size()-1;i>=0;--i) {
int v=G[x][i];
if(vis[v]) continue;
dep[v]=1;
d1[v]=a[v];
d2[v]=0;
getans(v, x);
d3[v]=a[x]+2ll*a[v];
d4[v]=a[x]+a[v];
update(v, x);
}
T.CLR(),cnt=1;
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(vis[v]) continue;
sn=size[x], root=0;
getroot(v, x);
dfs(root);
}
}
int main() {
// setIO("input");
scanf("%d",&n);
for(int i=1;i<n;++i) {
int x,y;
scanf("%d%d",&x,&y);
G[x].pb(y);
G[y].pb(x);
}
for(int i=1;i<=n;++i) {
scanf("%lld",&a[i]);
ans=max(ans, a[i]);
}
sn=n;
f[root=0]=N;
getroot(1, 0);
dfs(root);
printf("%lld\n",ans);
return 0;
}