2021.12.12周测

变懒了,直接放图了

A.

Problem

oOryWR.png

\(1 \leq N,M \leq 1000000, 1 \leq K \leq 100000\)

Solution

定位:签到题 (虽然我做了近一个多小时)

我们将题目要求的式子列出来:
\(\sum_{i=1}^n \sum_{j=1}^m (r[i]*l[j]*((i-1)*m+j))\)
其中\(r[i]\)表示第\(i\)行要乘上的数,\(l[j]\)表示第\(j\)列要乘上的数
根据乘法分配率可将\(r[i]\)提出来:
\(\sum_{i=1}^n (r[i] * \sum_{j=1}^m (l[j]*((i-1)*m+j))) = \sum_{i=1}^n (r[i] * \sum_{j=1}^m (l[j] * j + (i-1)*m*l[j]))\)
发现可以预处理出\(l[j]*j\)\(l[j]*m\)的值。
然后就没了。

\(code:\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1000000 + 5;
const int MAX_K = 100000 + 5;
const ll mod = 1e9 + 7;
int n,m,k;
ll row[MAX_N],col[MAX_N];
int main(){
//	freopen("matrixgame.in","r",stdin);
//	freopen("matrixgame.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++) row[i]=1;
	for(int j=1;j<=m;j++) col[j]=1;
	for(int i=1;i<=k;i++){
		char op[3];
		scanf("%s",op);
		int x;ll y;
		scanf("%d%lld",&x,&y);
		if(op[0]=='R') row[x]=row[x]*y%mod;
		else col[x]=col[x]*y%mod;
	}
	ll mul1=0,mul2=0,ans=0;
	for(int j=1;j<=m;j++) mul1=(mul1+1ll*m*col[j]%mod)%mod;
	for(int j=1;j<=m;j++) mul2=(mul2+1ll*j*col[j]%mod)%mod;
	for(int i=1;i<=n;i++){
		ll mul=(1ll*(i-1)*mul1%mod+mul2)%mod;
		ans=(ans+row[i]*mul%mod)%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

B.

oOrXm8.png

定位:观察分析总结题目性质,动态规划(因为一个\(continue\) 从100pts挂到0pts的惨案)

首先有个很显然的性质:去和回的路线一样。即,怎么去的怎么回来。

题目中说可以上下左右四个方向移动,又要让收割韭菜最多,那么手玩几组后发现,最优路线必定是经过某些点后,在两点之间反复横跳,最后回到沿之前的路回到终点。

于是就可以定状态了。
\(f[k][i][j]:\)表示第\(k\)步走到\((i,j)\)能收割的最多韭菜。
\(f[k][i][j]=\max\{f[k-1][i][j+1],f[k-1][i][j-1],f[k-1][i-1][j],f[k-1][i+1][j] \}\)

考虑计算反复横跳的贡献,设从\((x,y)\)\((i,j)\)反复横跳(且\((x,y)\)\((i,j)\)联通)
则贡献为\((f[k][i][j]*2-a[i][j]+((T-2*k)/2)*(a[i][j]+a[x][y])\)

\(code:\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 100 + 5;
const ll inf = 1e18;
int n,m,x,y,T;
ll f[2][MAX_N][MAX_N],a[MAX_N][MAX_N];
inline ll mymax(ll A,ll B,ll C,ll D){return max(A,max(B,max(C,D)));}
int main(){
//	freopen("leeks.in","r",stdin);
//	freopen("leeks.out","w",stdout);
	scanf("%d%d%d%d%d",&n,&m,&x,&y,&T);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%lld",&a[i][j]);
	for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				f[0][i][j]=-inf;
	f[0][x][y]=0;
	ll ans=0;
	for(int k=1;k<=min(n*m,T);k++){
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				f[k&1][i][j]=-inf;
		if(2*k>T) break;
		for(int i=1;i<=n;i++){
			if(abs(i-x)>k) continue;
			for(int j=1;j<=m;j++){
				if(abs(i-x)+abs(j-y)>k) continue;
				f[k&1][i][j]=mymax(f[k-1&1][i][j+1],f[k-1&1][i][j-1],f[k-1&1][i-1][j],f[k-1&1][i+1][j])+a[i][j];
				ll las=mymax(a[i][j+1],a[i][j-1],a[i-1][j],a[i+1][j]);
				ans=max(ans,2*f[k&1][i][j]-a[i][j]+1ll*((T-2*k)/2)*(a[i][j]+las));
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

C

oOrj0S.png

定位:\(dfs\)

考虑\(lca\)的位置,若金额朝上硬币\(y\)在询问节点\(x\)的子树内,则\(lca\)就是\(x\),若在子树外,但是是\(x\)的父亲节点,那么\(lca\)\(y\),否则为\(lca(x,y)\)

要求最近,可以将所有金额朝上的硬币放入数据结构中维护(删除,插入,找最近),可以采用\(set\)

\(dfs\)序插入\(set\)中,查询第一个大于和小于\(x\)\(dfs\)序的节点,找出\(lca\)最深的一个即可。

为什么这样子找出来深度最深一定是两个中一个?

因为若\(y\)\(x\)子树内,\(lca\)\(x\),因为一个节点的\(lca\)不可能比它深度深,所以深度最深为\(x\),若\(y\)不在\(x\)子树内,但是是\(x\)的父亲,则\(y\)节点的\(lca\)不可能比它深度深,所以最深为\(y\)。若\(y\)在另一颗子树内,这时候,先遍历到的点一定不会劣于后遍历到的点。

\(code:\)

#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 300000 + 5;
const int MAX_K = 20 + 5;
int n,m,lg[MAX_N],Time;
int Last[MAX_N],Next[MAX_N<<1],End[MAX_N<<1],tot;
inline void addedge(int x,int y){End[++tot]=y,Next[tot]=Last[x],Last[x]=tot;}
int in[MAX_N],id[MAX_N],dep[MAX_N],f[MAX_N][MAX_K];
set<int> s;
void dfs(int x){
	in[x]=++Time;
	id[Time]=x;
	dep[x]=dep[f[x][0]]+1;
	for(int i=1;i<=lg[dep[x]];i++) f[x][i]=f[f[x][i-1]][i-1];
	for(int i=Last[x];i;i=Next[i]){
		int y=End[i];
		if(y!=f[x][0]){
			f[y][0]=x;
			dfs(y);
		}
	}
}
inline int getlca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	while(dep[u]>dep[v]) u=f[u][lg[dep[u]-dep[v]]-1];
	if(u==v) return u;
	for(int i=lg[dep[u]]-1;i>=0;i--)
		if(f[u][i]!=f[v][i])
			u=f[u][i],v=f[v][i];
	return f[u][0];
}
int main(){
	for(int i=1;i<=300000;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n;i++){
		int f;
		scanf("%d",&f);
		addedge(f,i);
		addedge(i,f);
	}
	dfs(1);
	while(m--){
		
		int x;
		scanf("%d",&x);
		if(x>0){
			if(s.count(in[x])) s.erase(in[x]);
			else s.insert(in[x]);
		}
		else{
			int ans=0,pos=0;
			x=-x;
			if(s.count(in[x])){
				printf("%d\n",x);
				continue;
			}
			if(s.upper_bound(in[x])!=s.end()){
				auto y=s.upper_bound(in[x]);
				int lca=getlca(x,id[*y]);
				if(ans<dep[lca]) ans=dep[lca],pos=lca;
			}
			auto y=s.upper_bound(in[x]);
			if(y!=s.begin()){
				y--;
				int lca=getlca(x,id[*y]);
				if(ans<dep[lca]) ans=dep[lca],pos=lca;
			}
			printf("%d\n",pos);
		}
	}
	return 0;
}

D

oOrLOf.png

定位:左偏树 + 贪心 (不会)

话说nodgd的\(std\)被zhangzhou爆锤来着??

在链上,其实就是一个经典问题,但是在树上,就不是了。

\(nodgd\)的做法不是很懂,所以直接写\(zhangzhou\)的贪心了。

直接把贪心策略给出吧:

  1. \(x\)子树的堆合并到\(x\)
  2. 若堆顶大于当前\(a[x]\),则将堆顶\(pop\)\(push(a[x])\),贡献为\(|a[x]-top()|\)
  3. \(push(a[x])\)

至于证明和解释嘛... (咕了)

我做题需要证明?——\(Greedy King\)

\(code:\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1000000 + 5;
int n,a[MAX_N];
ll ans;
int Last[MAX_N],Next[MAX_N],End[MAX_N],tot;
inline void addedge(int x,int y){End[++tot]=y,Next[tot]=Last[x],Last[x]=tot;}
int val[MAX_N],dist[MAX_N],ls[MAX_N],rs[MAX_N],Newid;
class LeftistTree{
	int root;
	int merge(int x,int y){
		if(!x || !y) return x|y;
		if(val[x]<val[y]) swap(x,y);
		rs[x]=merge(rs[x],y);
		if(dist[ls[x]]<dist[rs[x]]) swap(ls[x],rs[x]);
		dist[x]=dist[rs[x]]+1;
		return x;
	}
	public:
		inline int top(){return val[root];}
		inline void pop(){root=merge(ls[root],rs[root]);}
		inline void push(int x){
			val[++Newid]=x;
			ls[Newid]=rs[Newid]=0;
			dist[Newid]=0;
			root=merge(root,Newid);
		}
		inline void push(LeftistTree x){
			root=merge(root,x.root);
		}
};
LeftistTree q[MAX_N];
void dfs(int x){
	bool leaf=1;
	for(int i=Last[x];i;i=Next[i]){
		int y=End[i];
		leaf=0;
		dfs(y);
		q[x].push(q[y]);
	}
	if(!leaf && q[x].top()>a[x]){
		ans+=q[x].top()-a[x];
		q[x].pop();
		q[x].push(a[x]);
	}
	q[x].push(a[x]);
}
int main(){
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		int f;
		scanf("%d",&f);
		addedge(f,i);
	}
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	dfs(1);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-12-13 16:03  Thermalrays  阅读(49)  评论(0编辑  收藏  举报