【Codeforces】 Educational Round 54 Div. 2 A-G

传送门:cf1076


A. Minimizing the String

贪心删去第一个字典序大于后一个位置的字母的位置。

#include<bits/stdc++.h>
using namespace std;

int n;
char s[200005];

int main(){
	int i,j;
	scanf("%d%s",&n,s+1);
    for(i=1;i<n;++i)
     if(s[i]>s[i+1]) break;
    for(j=1;j<=n;++j)
     if(i!=j) putchar(s[j]);
    return 0;
}

B.Divisor Subtraction

特殊性质:
偶数次数 n/2
奇数 (n-n的最小质因数)/2+1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;

ll n,ans;
int p[N],tot;
bool pri[N];

inline void pre()
{
	int i,j,res;
	for(i=2;i<N;++i){
		if(!pri[i]) p[++tot]=i;
		for(j=1;j<=tot && (ll)i*p[j]<N;++j){
			res=i*p[j];pri[res]=true;
			if(i%p[j]==0) break;
		}
	}
}

void sol(ll n)
{
	int i;
	if(!(n&1)) ans=n>>1;
	else{
		for(i=1;i<=tot;++i) if(n%p[i]==0) break;
	    if(i<=tot){
	       ans=1+(n-p[i])/2;
		}else{
			ans=1;
		}
	} 
	 
 }

int main(){
	int i,j;pre();
	scanf("%I64d",&n);
	sol(n);
	printf("%I64d",ans);
    return 0;
}

C.Meme Problem

解个二元一次方程。

#include<bits/stdc++.h>
using namespace std;
#define db double

int t,d;

int main(){
	db a,b,c,g,ans;
	scanf("%d",&t);
	for(;t;--t){
		scanf("%d",&d);
		a=1.0;b=-(db)d;c=-b;
		g=sqrt(b*b-4*c);
		ans=(-b+g)/2.0;
		if(ans>=0.0 && ans<=(db)d){
			printf("Y %.10lf %.10lf\n",ans,d-ans);
		}else{
			ans=(-b-g)/2.0;
			if(ans>=0.0 && ans<=(db)d)
			 printf("Y %.10lf %.10lf\n",ans,d-ans);
		    else printf("N\n");
		}
	}
	return 0;
}

D.Edge Deletion

再也不写SPFA了!!!
跑个最短路树再贪心取

By ccosi, contest: Educational Codeforces Round 54 (Rated for Div. 2), problem: (D) Edge Deletion, Accepted, #
 #include<bits/stdc++.h>
using namespace std;
const int N=3e5+100;
typedef long long ll;

int n,m,K,f[N],sz[N],ans[N],cot;
int head[N],to[N<<1],nxt[N<<1],w[N<<1],tot;
ll dis[N],inf;

struct P{
    int u;ll d;
    bool operator<(const P&ky)const{
	     return ky.d<d;
	}
}temp;

struct LE{
	int u,v,w;
	bool operator<(const LE&ky)const{
		if(u!=ky.u) return u<ky.u;
		if(v!=ky.v) return v<ky.v;
	   return w<ky.w;
	}
};

map<LE,int>mp;
priority_queue<P>que;

inline void lk(int u,int v,int vv)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=vv;}

inline void spfa()
{
	int i,j,x,y;
	memset(dis,0x7f,sizeof(ll)*(n+3));
	inf=dis[1];dis[1]=0LL;que.push((P){1,0});
	for(;que.size();){
		temp=que.top();que.pop();x=temp.u;
		if(dis[x]!=temp.d) continue;
		for(i=head[x];i;i=nxt[i]){
			j=to[i];if(dis[j]<=dis[x]+w[i]) continue;
			dis[j]=dis[x]+w[i];
			que.push((P){j,dis[j]});
		}
	}
}

int getfa(int x){return x==f[x]?x:(f[x]=getfa(f[x]));}

void ck(int x,int fr)
{
	for(int j,i=head[x];i;i=nxt[i]){
		j=to[i];if(j==fr) continue;
		printf("%d ",w[i]);ck(j,x);
	} 
}

bool inq[N];
queue<int>Q;

int main(){
	int i,j,x,y,z,pr;
	scanf("%d%d%d",&n,&m,&K);
	for(i=1;i<=m;++i){
		scanf("%d%d%d",&x,&y,&z);
		mp[(LE){x,y,z}]=i;mp[(LE){y,x,z}]=i;
		lk(x,y,z);lk(y,x,z);
	}
	spfa();
	Q.push(1);inq[1]=1;
	for(;Q.size();){
	    if(cot==K) break;
		x=Q.front();Q.pop();
	    for(i=head[x];i;i=nxt[i]){
	    	j=to[i];if(dis[j]>=inf || inq[j] || dis[j]!=dis[x]+w[i]) continue;
	    	cot++;
			ans[cot]=mp[(LE){x,j,w[i]}];
			Q.push(j);inq[j]=1;
			if(cot==K) break; 
		}
	} 
	printf("%d\n",cot);
	for(i=1;i<=cot;++i) printf("%d ",ans[i]);
	return 0;
} 

E.Vasya and a Tree

将每个结点转成平面上的一个点的深度看做 y y y坐标, d f s dfs dfs序为 x x x坐标,每次操作相当于一个矩阵区域的加值。最终查询矩阵每个点的值。
离线下来线段树维护即可。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
typedef long long ll;

int n,m,dep[N],df[N],ot[N],dfn,cnt;
int head[N],to[N<<1],nxt[N<<1],tot;
ll ss,ans[N],bit[N];

struct P{
	int r,c,v;
	P(int r_=0,int c_=0,int v_=0):r(r_),c(c_),v(v_){};
	bool operator <(const P&ky)const{
	     return r<ky.r;
	}
}q[N<<2],p[N];

inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}

inline void ad(int x,ll v)
{for(;x<=n;x+=(x&(-x))) bit[x]+=v;}
inline ll ask(int x)
{
	ll re=0LL;
	for(;x;x-=(x&(-x))) re+=bit[x];
	return re;
}

void dfs(int x,int fa)
{
	df[x]=++dfn;
	for(int j,i=head[x];i;i=nxt[i]){
		j=to[i];if(j==fa) continue;
		dep[j]=dep[x]+1;dfs(j,x);
	}
	ot[x]=dfn;
}

int main(){
	int i,j,x,y,z;
	scanf("%d",&n);
	for(i=1;i<n;++i){
		scanf("%d%d",&x,&y);
		lk(x,y);lk(y,x);
	}
	dep[1]=1;dfs(1,0);
	scanf("%d",&m);
	for(i=1;i<=m;++i){
		scanf("%d%d%d",&x,&y,&z);
		q[++cnt]=P(dep[x],df[x],z);
		if(ot[x]<n){
		q[++cnt]=P(dep[x]+y+1,ot[x]+1,z),
		q[++cnt]=P(dep[x],ot[x]+1,-z);	
		}
		q[++cnt]=P(dep[x]+y+1,df[x],-z);
	}
	sort(q+1,q+cnt+1);
	for(i=1;i<=n;++i) p[i]=P(dep[i],df[i],i);
	sort(p+1,p+n+1);
	
	for(i=j=z=1;z<=n;i=j){
	    for(;j<=cnt && q[j].r==p[z].c;++j) 
		  ad(q[j].c,q[j].v);
	    for(;z<=n && p[z].r<=q[i].r;++z) 
	     ans[p[z].v]=ask(p[z].c);
	}
	for(i=1;i<=n;++i) printf("%I64d ",ans[i]);
	return 0;	
}

F.Summer Practice Report

两次贪心取。
一次贪心 T T T后跟 k k k F F F。判每次剩下的 F F F的个数。
一次贪心 F F F后跟 k k k T T T。判每次剩下的 T T T的个数。
若两次都合法,就有解。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
typedef long long ll;

int n,K,a[N],b[N];
ll f[N];

int main(){
	int i,j,la,ra,lb,rb,x,y,pr=0;
	scanf("%d%d",&n,&K);
	for(i=1;i<=n;++i) scanf("%d",&a[i]);
	for(i=1;i<=n;++i) scanf("%d",&b[i]);
	for(i=1;i<=n;++i){
		f[i]=max(0LL,f[i-1]+a[i]-(ll)K*b[i]);
		if(f[i]>K) {puts("NO");return 0;}
	}
	for(i=1;i<=n;++i){
		f[i]=max(0LL,f[i-1]+b[i]-(ll)K*a[i]);
		if(f[i]>K) {puts("NO");return 0;}
	}
	puts("YES");
}

G.Array Game

考虑单组询问的做法,设 d p [ i ] [ j ] dp[i][j] dp[i][j]为当前在 b i b_i bi处,且 b i = j b_i=j bi=j时的状态, 1 1 1表示必胜, 0 0 0表示必败。

显然答案与 j j j的具体值无关,只与 j j j的奇偶性有关, d p [ i ] [ j ] dp[i][j] dp[i][j]的第二维 j j j可以省略,只需要记录每个点 D P i = d p [ i ] [ b i   x o r   1 ] DP_i=dp[i][b_i\ xor \ 1] DPi=dp[i][bi xor 1]的值。

D P i = ( b i   x o r 1 ) ∣ ( D P i + 1   x o r   1 ) ∣ ( D P i + 2   x o r   1 ) . . . ( D P i + m   x o r   1 ) DP_i=(b_i\ xor 1)|(DP_{i+1}\ xor \ 1)|(DP_{i+2}\ xor \ 1)...(DP_{i+m}\ xor\ 1) DPi=(bi xor1)(DPi+1 xor 1)(DPi+2 xor 1)...(DPi+m xor 1)

  1. m m m个位置中有一个位置 j j j满足 d p [ j ] [ b j − 1 ] = 0 dp[j][b_j-1]=0 dp[j][bj1]=0,则当前状态必胜。
  2. 后继状态全为必胜,则只有 b i b_i bi为偶数( b i   x o r   1 b_i\ xor \ 1 bi xor 1为奇数)时,能够达到必胜状态。

于是从后往前 d p dp dp,得到了一个 O ( n m ) O(nm) O(nm)的做法。

区间操作显然要上颗线段树,发现只需要记录一个关键信息“后继状态中最近的必败状态和当前位置之间的距离 d i s dis dis”,于是考虑对于每个结点构造一个函数 g [ i ] g[i] g[i],分别表示初始状态 d i s dis dis i i i时,倒序 d p dp dp转移完该结点管辖区间后的状态的 d i s dis dis
这样每次就可以 O ( m ) O(m) O(m)合并了。

考虑对于单个点的初始化:
g [ i ] = i + 1 ( 1 ≤ i ≤ m ) g[i]=i+1(1\leq i\leq m) g[i]=i+1(1im)(情况1.)
g [ m + 1 ] g[m+1] g[m+1]表示后继状态全为必胜的状态。
b i b_i bi为奇数时, g [ m + 1 ] = 0 g[m+1]=0 g[m+1]=0
b i b_i bi为偶数时, g [ m + 1 ] = 1 g[m+1]=1 g[m+1]=1

对于区间加值的操作:偶数之间忽略,奇数则区间取反。所以需要每个结点维护区间全部值取反后的 g ′ g&#x27; g函数,区间加操作转换成了区间 s w a p ( g , g ′ ) swap(g,g&#x27;) swap(g,g),打个 l a z y t a g lazytag lazytag后标记永久化即可。

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
#define lc k<<1
#define rc k<<1|1
using namespace std;
const int N=2e5+20;
typedef long long ll;

int n,m,q,rv[N<<2];ll a[N];

struct node{
	int g[2][6];
	inline void itia(int op){
		for(int i=0;i<m;++i) g[0][i]=g[1][i]=i+1;
		g[0][m]=m;g[1][m]=0;
		if(op) g[0][m]=0,g[1][m]=m;
    }
    inline void flp()
    {for(int i=0;i<=m;++i) swap(g[0][i],g[1][i]);}
}t[N<<2],temp;

inline node mg(node a,node b)
{
	node re;int i,j;
	for(i=0;i<2;++i)
	 for(j=0;j<=m;++j)
	  re.g[i][j]=a.g[i][b.g[i][j]];
	return re;
}

inline void build(int k,int l,int r)
{
	if(l==r) {t[k].itia((int)(a[l]&1));return;}
	build(lc,l,mid);build(rc,mid+1,r);
	t[k]=mg(t[lc],t[rc]);
}

inline void cg(int k,int l,int r,int L,int R)
{
	if(L<=l && r<=R) {rv[k]^=1;t[k].flp();return;}
    if(L<=mid) cg(lc,l,mid,L,R);
    if(R>mid) cg(rc,mid+1,r,L,R);
    t[k]=mg(t[lc],t[rc]);
    if(rv[k]) t[k].flp();
}

inline node ask(int k,int l,int r,int L,int R)
{
	if(L<=l && r<=R) return t[k];
	node re;
	if(R<=mid) re=ask(lc,l,mid,L,R);
	else if(L>mid) re=ask(rc,mid+1,r,L,R);
	else re=mg(ask(lc,l,mid,L,R),ask(rc,mid+1,r,L,R));
	if(rv[k]) re.flp();
	return re;
}

int main(){
	int i,j,op,l,r;ll z;
	scanf("%d%d%d",&n,&m,&q);
	for(i=1;i<=n;++i) scanf("%I64d",&a[i]);
	build(1,1,n);
	for(;q;--q){
		scanf("%d%d%d",&op,&l,&r);
		if(op==1){
			scanf("%I64d",&z);
			if(z & 1) cg(1,1,n,l,r);
		}else puts(ask(1,1,n,l,r).g[0][m]?"1":"2");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值