2021.12.12周测
变懒了,直接放图了
A.
Problem
\(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.
定位:观察分析总结题目性质,动态规划(因为一个\(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
定位:\(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
定位:左偏树 + 贪心 (不会)
话说nodgd的\(std\)被zhangzhou爆锤来着??
在链上,其实就是一个经典问题,但是在树上,就不是了。
\(nodgd\)的做法不是很懂,所以直接写\(zhangzhou\)的贪心了。
直接把贪心策略给出吧:
- 将\(x\)子树的堆合并到\(x\)
- 若堆顶大于当前\(a[x]\),则将堆顶\(pop\),\(push(a[x])\),贡献为\(|a[x]-top()|\)
- \(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;
}