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\n",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\n",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\n",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\n",f[id[n]]);
}
来源:oschina
链接:https://my.oschina.net/u/4342956/blog/3919678