AtCoder Regular Contest 098

AtCoder Regular Contest 098


C - Attention

题意:有n个人站成一排,每个人可能面向东或西。

现在要选一个人,让其他人转向所选的那个人。求最小转向次数。

分析:模拟即可。

代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cstdlib>
    #include <map>
    using namespace std;
    typedef long long ll;
    #define GG puts("FUCK")
    #define N 300050
    char str[N];
    int n,sum;
    int main() {
    	scanf("%d%s",&n,str+1);
    	int i;
    	for(i=1;i<=n;i++) {
    		if(str[i]=='E') sum++;
    	}
    	int now=0,ans=1<<30;
    	for(i=1;i<=n;i++) {
    		if(str[i]=='E') sum--;
    		ans=min(ans,now+sum);
    		if(str[i]=='W') now++;
    	}
    	printf("%d
",ans);
    }

D - Xor Sum 2

题意:给你一个长度为n的序列,求合法连续子序列个数,合法定义为和等于他们异或起来。

分析:可以发现这样的序列长度最多为20,暴力/双指针即可。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <map>
using namespace std;
typedef long long ll;
#define GG puts("FUCK")
#define N 200050
int n,a[N],h[30],num;
void add(int x) {
	int i;
	for(i=19;i>=0;i--) if(x&(1<<i)) {
		h[i]++; if(h[i]==2) num++;
	}
}
void del(int x) {
	int i;
	for(i=19;i>=0;i--) if(x&(1<<i)) {
		h[i]--; if(h[i]==1) num--;
	}
}
int main() {
	scanf("%d",&n);
	int i;
	for(i=1;i<=n;i++) {
		scanf("%d",&a[i]);
	}
	ll ans=0;
	int j=1;
	for(i=1;i<=n;i++) {
		add(a[i]);
		while(j<=i&&num) del(a[j]),j++;
		ans+=(i-j+1);
	}
	printf("%lld
",ans);
}

E - Range Minimum Queries

题意:给你一个长度为n的序列,你可以进行Q次操作,每次操作把一段长度为K的连续子序列的最小值删除。

求删掉的数中最大-最小的最小值。

分析:枚举删除的最小值,然后比这个数小的不能被选到,相当于把序列分成几段,每段把可以删的放在一起。

然后拿第Q小的能删的数更新答案。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 2050
int n,a[N];
int K,Q,b[N],c[N],ans=1<<30,tot;
void add(int l,int r) {
	int i;
	for(i=l;i<=r;i++) b[i]=a[i];
	sort(b+l,b+r+1);
	for(i=l;i<=r-K+1;i++) c[++tot]=b[i];
}
void solve(int x) {
	int i,lst=1; tot=0;
	for(i=1;i<=n;i++) {
		if(a[i]<x) {
			if(lst<=i-1) add(lst,i-1);
			lst=i+1;
		}
	}
	if(lst<=n) add(lst,n);
	sort(c+1,c+tot+1);
	if(tot<Q) return ;
	ans=min(ans,c[Q]-c[1]);
}
int main() {
	int i;
	scanf("%d%d%d",&n,&K,&Q);
	for(i=1;i<=n;i++) scanf("%d",&a[i]);
	for(i=1;i<=n;i++) {
		solve(a[i]);
	}
	printf("%d
",ans);
}

F - Donation

题意:给出一个n个点,m条边的无向图。每个点有权值ai,bi。

你可以任选一个点当做起点,你初始拥有的钱需要大于等于aS。每次移动的时候钱数也要大于等于aT。

你需要对每个点捐出bi的钱,求初始最少有多少钱。

分析:

发现对于每个点在最后一次贡献不会更差。

把A的约束转化一下,设Ci=max(Ai-Bi,0)。这个约束表示任何时候站在i这个点至少要有Ci。

然后贪心的想,Ci大的先遍历显然不会更差。

于是这样:找到C最大的点x,把x删掉后产生了T个连通块G1,G2..GT。

显然有一种最优的方案是贡献x后进入了一个连通块就不再出来。

然后递归每个连通块,用这一层的根连向上一层的根,这样就形成了一棵树。

这棵树满足任意一个点的Ci大于等于子树的点的Cj。

可以DP,设f[x]表示x的子树符合条件的最小初始钱数。

初始值f[x]=s[x]+c[x],其中s[x]表示x的子树b之和。

然后考虑把儿子的贡献拽到x的后面,有f[x]=min(f[x],s[x]-s[to[i]]+max(f[to[i]],c[x]))

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
#define GG puts("FUCK")
#define N 100050
int head[N],to[N],nxt[N],fa[N],a[N],b[N],c[N],n,m,vis[N],id[N],cnt;
vector<int>v[N];
ll f[N],s[N];
int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
bool cmp(int x,int y) {return c[x]<c[y];}
inline void add(int u,int v) {
	to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt;
}
void dfs(int x) {
	int i; s[x]=b[x];
	for(i=head[x];i;i=nxt[i]) {
		dfs(to[i]); s[x]+=s[to[i]];
	}
	f[x]=s[x]+c[x];
	for(i=head[x];i;i=nxt[i]) {
		f[x]=min(f[x],s[x]-s[to[i]]+max(f[to[i]],1ll*c[x]));
	}
}
int main() {
	scanf("%d%d",&n,&m);
	int i,x,y,j;
	for(i=1;i<=n;i++) {
		scanf("%d%d",&a[i],&b[i]); c[i]=max(a[i]-b[i],0); fa[i]=id[i]=i;
	}
	for(i=1;i<=m;i++) {
		scanf("%d%d",&x,&y); v[x].push_back(y); v[y].push_back(x);
	}
	sort(id+1,id+n+1,cmp);
	for(i=1;i<=n;i++) {
		x=id[i]; int lim=v[x].size(); vis[x]=1;
		for(j=0;j<lim;j++) {
			y=v[x][j]; if(!vis[y]) continue;
			y=find(y);
			if(x!=y) {
				fa[y]=x; add(x,y);
			}
		}
	}
	dfs(id[n]);
	printf("%lld
",f[id[n]]);
}
原文地址:https://www.cnblogs.com/suika/p/9252551.html