2021牛客暑期多校训练营9 题解

H Happy Number

构造题,简单的写一下就能发现一位数有3个,二位数有9个,(i)位数有(3^i)个,这样先确定下是几位数,再从高到低依次确定每一位是2,3,6中的哪一个就行。

查看代码

	//不等,不问,不犹豫,不回头.
#include
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair
#define PII pair
#define ull unsigned long long
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(RE int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

int main()
{
freopen("1.in","r",stdin);
int get(n);
int p=3,now,z=3;
rep(i,1,100)
{
if(n<=p) {now=i;break;}
z=z3;p+=z;
}
n-=p-z;
fep(i,now,1)
{
z/=3;
if(n<=z) printf("%d",2);
else if(n>z&&n<=2
z) {printf("%d",3);n-=z;}
else if(n>2z) {printf("%d",6);n-=2z;}
}
return (0_0);
}
//以吾之血,铸吾最后的亡魂.

E Eyjafjalla

发现题目中的重要的一个特征就是从根节点往下走的话,它的温度一定是递减的,也就是说离根节点越近他的温度越高,这个性质必须要利用起来。给定询问是病毒爆发的城市以及病毒生存的温度范围[l,r],病毒可以向四周感染,那我们可以先找到它可以感染到的所有父亲,然后从父亲向下感染,由于向上温度是递增的,所以找到可以感染的深度最小的父亲后,我们直接统计他儿子的贡献就行。可以发现找到父亲的过程完全可以用倍增去找。找到父亲后我们考虑从父亲(记为(fa_{mx}))向下感染的过程,由于向下是递减的,我们只需要考虑他们的温度值是不是大于l即可。只要是该子树内并且温度大于l是不是一定能被感染,考虑子树内一个节点u,它的温度大于l,往上找(fa_{mx})时,由于向上走是递增的所以也一定大于l。这样的话我们只需要统计(fa_{mx})的子树内有多少节点的温度大于l即可。用dfs序可以将子树变成区间操作。也就是查询区间内有多少值大于l。这个用主席树写就行了。可以离线的话,我们完全可以将所有的询问按l从大到小排序,然后将所有的点从大到小排序,每次处理一个询问时,将所有温度>=这个询问l的都加到树状数组里面,然后查询时只需要查询区间和就行了。我们保证处理到一个询问时,大于等于l的点都已经被加进去就行了。

离线+树状数组

//不等,不问,不犹豫,不回头.
#include
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair
#define PII pair
#define ull unsigned long long
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=1e5+10;
int n,t[N],link[N],tot,f[N][25],c[N],dfn[N],num,Size[N],ans[N];
struct wy{int y,next;}a[N<<1];
struct dian{int t,id;}b[N];
struct query{int x,l,r,id;}q[N];

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

inline void add_edge(int x,int y)
{
a[++tot].y=y;a[tot].next=link[x];link[x]=tot;
}

inline void dfs(int x,int father)
{
dfn[x]=++num;
Size[x]=1;
go(x)
{
if(y==father) continue;
f[y][0]=x;
rep(j,1,20) f[y][j]=f[f[y][j-1]][j-1];
dfs(y,x);
Size[x]+=Size[y];
}
}

inline bool cmp2(query a,query b){return a.l>b.l;}

inline bool cmp1(dian a,dian b){return a.t>b.t;}

inline void add(int x,int v)
{
for(;x<=n;x+=(x&-x)) c[x]+=v;
}

inline int mx_fa(int x,int val)
{
fep(i,20,0) if(f[x][i]!=0&&t[f[x][i]]<=val) x=f[x][i];
return x;
}

inline int ask(int x)
{
int ans=0;
for(;x;x-=(x&-x)) ans+=c[x];
return ans;
}

int main()
{
//freopen("1.in","r",stdin);
get(n);
rep(i,1,n-1)
{
int get(x),get(y);
add_edge(x,y);add_edge(y,x);
}
rep(i,1,n) get(t[i]),b[i].id=i,b[i].t=t[i];
dfs(1,0);
int get(Q);
rep(i,1,Q)
{
get(q[i].x);get(q[i].l);get(q[i].r);
q[i].id=i;
}
sort(b+1,b+n+1,cmp1);
sort(q+1,q+Q+1,cmp2);
int now=1;
rep(i,1,Q)
{
while(now<=n&&b[now].t>=q[i].l)
{
add(dfn[b[now].id],1);
now++;
}
if(t[q[i].x]<q[i].l||t[q[i].x]>q[i].r) {ans[q[i].id]=0;continue;}
int fa=mx_fa(q[i].x,q[i].r);//寻找小于等于q[i].r的最大祖先
ans[q[i].id]=ask(dfn[fa]+Size[fa]-1)-ask(dfn[fa]-1);
}
rep(i,1,Q) put(ans[i]);
return (0_0);
}
//以吾之血,铸吾最后的亡魂.

主席树

//不等,不问,不犹豫,不回头.

include<bits/stdc++.h>

define _ 0

define ls p<<1

define db double

define rs p<<1|1

define P 1000000007

define ll long long

define INF 1000000000

define get(x) x=read()

define PLI pair<ll,int>

define PII pair<int,int>

define ull unsigned long long

define put(x) printf("%d ",x)

define putl(x) printf("%lld ",x)

define rep(x,y,z) for(int x=y;x<=z;++x)

define fep(x,y,z) for(int x=y;x>=z;--x)

define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)

using namespace std;
const int N=1e5+10;
int n,link[N],tot,te[N],num,b[N3],root[N],nm,dfn[N],Size[N],f[N][25],pre[N];
struct wy{int y,next;}a[N<<1];
struct query{int x,l,r;}q[N];
struct Tree{int lc,rc,dat;}t[N
20];

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

inline void add(int x,int y)
{
a[++tot].y=y;a[tot].next=link[x];link[x]=tot;
}

inline int find(int x){return lower_bound(b+1,b+num+1,x)-b;}

inline void dfs(int x,int father)
{
dfn[x]=++nm;pre[nm]=x;
Size[x]=1;
go(x)
{
if(y==father) continue;
f[y][0]=x;
rep(j,1,20) f[y][j]=f[f[y][j-1]][j-1];
dfs(y,x);
Size[x]+=Size[y];
}
}

inline int insert(int now,int l,int r,int x,int val)
{
int p=++tot;
t[p]=t[now];
if(l==r) {t[p].dat+=val;return p;}
int mid=l+r>>1;
if(x<=mid) t[p].lc=insert(t[now].lc,l,mid,x,val);
else t[p].rc=insert(t[now].rc,mid+1,r,x,val);
t[p].dat=t[t[p].lc].dat+t[t[p].rc].dat;
return p;
}

inline int mx_fa(int x,int val)
{
fep(i,20,0) if(f[x][i]!=0&&te[f[x][i]]<=val) x=f[x][i];
return x;
}

inline int ask(int q,int p,int l,int r,int val) //找大于等于val的个数。
{
if(l>=val) return t[p].dat-t[q].dat;
int mid=l+r>>1;
int ans=0;
if(val<=mid) ans+=ask(t[q].lc,t[p].lc,l,mid,val);
ans+=ask(t[q].rc,t[p].rc,mid+1,r,val);
return ans;
}

int main()
{
//freopen("1.in","r",stdin);
get(n);
rep(i,1,n-1)
{
int get(x),get(y);
add(x,y);add(y,x);
}
rep(i,1,n) get(te[i]),b[++num]=te[i];
int get(Q);
rep(i,1,Q)
{
get(q[i].x);get(q[i].l);get(q[i].r);
b[++num]=q[i].l;b[++num]=q[i].r;
}
sort(b+1,b+num+1);
num=unique(b+1,b+num+1)-b-1;
rep(i,1,n) te[i]=find(te[i]);
tot=0;dfs(1,0);
rep(i,1,n) root[i]=insert(root[i-1],1,num,te[pre[i]],1);
rep(i,1,Q)
{
int l=find(q[i].l),r=find(q[i].r);
int fa=mx_fa(q[i].x,r);
if(te[q[i].x]<l||te[q[i].x]>r) {put(0);continue;}
else put(ask(root[dfn[fa]-1],root[dfn[fa]+Size[fa]-1],1,num,l));
}
return (0_0);
}
//以吾之血,铸吾最后的亡魂.

J Jam

这个题题面比较冗长....简化后其实就是交通规则的问题。
经过简单的推,发现几个规律:
1.向右走的灯一定亮。(接下来只考虑直走和向左走的情况)
2.只有其中一路的直走和向左走亮。
3.只有两个对路的直走亮。
4.只有两个对路的左走亮。
5.只有一路直走,顺时针的下一路左走亮。
比赛时到这就挂了...这给我提了很大的醒,很多东西只停留在意识上是不行的,我们需要将其数学化,数字化,就举这个题为例,想到这,很显然的是我们可以不管向右走的操作,到最后处理一下就行。接下来只考虑直走和左走,我们可以用数组将每个路的直走和左走的数量记录下来。
如下:方向我们用0,1,2,3来表示。用数组a表示直走的车辆,用数组b表示左走的数量。
则问题转化成以下问题:给定
(a_0,a_1,a_2,a_3)
(b_0,b_1,b_2,b_3)
至于下标为何从0开始,因为可以取余的关系,从零开始比较方便。
我们右四个操作,1.(a_i)--,(b_i)--
2.(a_i)--,(a_{i+2})--
3.(b_i)--,(b_{i+2})--
4.(a_i)--,(b_{i+1})--
问最少多少次可以将上面八个数都变成0。
我们发现假使没有第四个操作,剩下的操作都是在(a_i,a_{i+2},b_i,b_{i+2})这四个数里面发生的。那我们完全可以将这八个数分成两个2( imes) 2的矩阵。分别是
(a_0,a_2|a_1,a_3)
(b_0,b_2|b_1,b_3)
这样的话以上操作就变成了我们每次可以选择小矩阵中的一行或者一列减1,求最少多少次将矩阵中的数字减完。其实答案就是max(对角线的和)。考虑一下(a_0)(b_2)都可以去影响(a_2,b_0)他们两个对角线的和大的一定能将和小的减完,剩下的只能自己消化。
既然不考虑操作四的话,我们可以这样快速的O(1)的查询到答案。
考虑操作4,看数据范围为100,直接枚举,咦,这好像有点不对,(100^5)一定爆炸啊...发现当我们四个a都用操作四时,一定会有一个最小值,其实都用的话就等效于全部数都减去这个最小值,之后剩下三个接着用就行了。既然全部值都减去这个最小值,我们大可以都用其他操作将其顶替,这样的话,我们只考虑那剩下的三个用多少次就可以了。总复杂度O(100^4)4秒能过。

//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=5;
int d[N][N],a[N],b[N];
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}
 
inline int ask(int a,int b,int c,int d)
{
    a=max(a,0);b=max(b,0);
    c=max(c,0);d=max(d,0);
    return max(a+d,b+c);
}
 
int main()
{
    //freopen("1.in","r",stdin);
    int get(T);
    while(T--)
    {
        int mx=0;
        rep(i,0,3) rep(j,0,3) get(d[i][j]),mx=max(mx,d[i][j]);
        rep(i,0,3)
        {
            a[i]=d[i][(i+2)%4];
            b[i]=d[i][(i+1)%4];
        }
        int ans=INF;
        rep(i,0,mx) rep(j,0,mx) rep(k,0,mx) //枚举操作四用了多少次。
        {
            ans=min(ans,i+j+k+ask(a[0]-i,a[2]-k,b[0],b[2]-j)+ask(a[1]-j,a[3],b[1]-i,b[3]-k));
            ans=min(ans,i+j+k+ask(a[0],a[2]-j,b[0]-k,b[2]-i)+ask(a[1]-i,a[3]-k,b[1],b[3]-j));
            ans=min(ans,i+j+k+ask(a[0]-k,a[2]-i,b[0]-j,b[2])+ask(a[1],a[3]-j,b[1]-k,b[3]-i));
            ans=min(ans,i+j+k+ask(a[0]-j,a[2],b[0]-i,b[2]-k)+ask(a[1]-k,a[3]-i,b[1]-j,b[3]));
        }
        rep(i,0,3) ans=max(ans,d[i][(i+3)%4]);
        put(ans);
    }
    return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.

之后我们考虑另一种解法,考虑每一种操作都是将两个数减一,我们不能随意安排操作顺序的原因就是这个数可以和这个数搭配,也可以和另一个数搭配,随意安排和谁搭配的的话可能不是最优的情况。这点就有点像是匹配的问题。我们尝试用网络流去解决,只考虑我们最多能用多少次策略。这个时候我们就需要将所有的点都分成两部分,一部分向另一部分连边,从源点向一部分点连边,边的容量定义为这个点对应的车的数量,另一部分向汇点连边,之后根据已有的策略将两部分的点连边,边的容量为无限大。这样的话跑出来的最大流就是我们能够使用最多的策略的个数。我们考虑将已有的图划分成二分图,发现不合法啊,尽可能的减少不合法的边数,通过枚举不合法的边数的方法来去掉不二法的边。总的复杂度为O(n^2C)C为网络流的复杂度。

//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=15;
int p[N][N],T,A[N],B[N],tot,link[N],s,t,d[N],current[N];
struct bian{int y,next,v;}a[N<<2]; 

inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}

inline void add(int x,int y,int v)
{
	a[++tot].y=y;a[tot].v=v;a[tot].next=link[x];link[x]=tot;
	a[++tot].y=x;a[tot].v=0;a[tot].next=link[y];link[y]=tot;
}

inline void solve_edge()
{
	add(s,1,A[0]);add(s,6,B[2]);add(s,7,A[3]);add(s,8,B[3]);
	add(2,t,B[0]);add(3,t,A[1]);add(4,t,B[1]);add(5,t,A[2]);
	add(1,2,INF);add(6,5,INF);
	add(1,5,INF);add(6,2,INF);add(7,3,INF);add(8,4,INF);
	add(1,4,INF);add(6,3,INF);add(8,5,INF);add(7,2,INF);
}

inline bool bfs()
{
	queue<int>q;q.push(s);
	memset(d,0,sizeof(d));
	memcpy(current,link,sizeof(current));
	d[s]=1;
	while(q.size())
	{
		int x=q.front();q.pop();
		for(int i=link[x];i;i=a[i].next)
		{
			int y=a[i].y;
			if(d[y]||!a[i].v) continue;
			d[y]=d[x]+1;
			q.push(y);
			if(y==t) return true;
		}
	}
	return false;
}

inline int dinic(int x,int flow)
{
	if(x==t) return flow;
	int rest=flow,k;
	for(int i=current[x];i&&rest;i=a[i].next)
	{
		current[x]=i;
		int y=a[i].y;
		if(d[y]==d[x]+1&&a[i].v)
		{
			k=dinic(y,min(rest,a[i].v));
			if(!k) d[y]=0;
			a[i].v-=k;
			a[i^1].v+=k;
			rest-=k;
		}
	}
	return flow-rest;
}

int main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
	get(T);
	while(T--)
	{
		rep(i,0,3) rep(j,0,3) get(p[i][j]);
		rep(i,0,3)
		{
			A[i]=p[i][(i+2)%4];
			B[i]=p[i][(i+1)%4];
		}
		int ans=INF;
		rep(i,0,100) rep(j,0,100)
		{
			rep(k,0,3) A[k]=p[k][(k+2)%4],B[k]=p[k][(k+1)%4];
			A[1]=max(0,A[1]-i);
			B[1]=max(0,B[1]-i);
			A[3]=max(0,A[3]-j);
			B[3]=max(0,B[3]-j);
			tot=1;s=0;t=10;
			memset(link,0,sizeof(link));
			solve_edge();
			int flow,maxflow=0;
			while(bfs())
				while(flow=dinic(s,INF)) maxflow+=flow;	
			int sum=0;
			rep(k,0,3) sum+=A[k]+B[k];	
			ans=min(ans,i+j+sum-maxflow);	
		}
		rep(i,0,3) ans=max(ans,p[i][(i+3)%4]);
		put(ans);
	}
    return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.

原文地址:https://www.cnblogs.com/gcfer/p/15143137.html