新来Ag大佬,下午写了三小时成绩是我三倍(捂脸
T1.淘淘种地
题目大意:给出一个矩阵,每次选出一个子矩阵与一个权值,如果矩阵内权值与给出权值不同,则变成(-1),求最后有多少个数变成了(-1)。
本题有一个部分分,就是矩阵内权值只有(1)和(2),也就是说只有两种情况,我们其实也可以看成(1)和(0),这样就变成了一个比较直观的问题。
我们可以发现,题目中要求的只是最后有多少个数变成了(-1),这就提醒我们可以最后一起统计答案,所以二维差分就变成了一个不错的选择。
将(0)和(1)分成两部分计算,然后答案取并就好了。开两个数组,分别保存(0)和(1)的差分值,最后前缀和统计答案,如果这个点(a_{ij}~xor~1)后的值的覆盖了这个位置的话,答案(+1)。
现在我们来看正解,通过运用上面的方法,我们想到把所有数二进制拆分,然后每一位进行一次上面的操作,最后答案取并就好了。时间复杂度为(O((nm+T)log{nm}))。
小结:
解题关键:想到二进制拆分每一个数统计答案
芝士点:差分
不知道是我打炸了还是咋滴,总是TLE或者RE,没办法,只能贴一个(60)分代码了 (逃~
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1000005;
inline int read()
{
re int x=0,f=1;
re char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-')f*=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
int n,m,T,aa,vis[N][20][2],ans,a[N][20],mid;
bool finvis[N];
inline int to(int x,int y)
{
if(x<1||y<1)return 0;
return (x-1)*m+y;
}
int main()
{
scanf("%d%d%d",&n,&m,&T);
for(re int i=1;i<=n;i++)
for(re int j=1;j<=m;j++)
{
aa=read();
for(re int k=0;k<20;k++)
a[to(i,j)][k]=(aa&(1<<k));
}
for(re int i=1,type,x1,x2,y1,y2;i<=T;i++)
{
x1=read(),y1=read(),x2=read(),y2=read(),type=read();
for(re int j=0;j<20;j++)
{
mid=(type&(1<<j));
vis[to(x1,y1)][j][mid]++;
if(x2+1<=n&&y2+1<=m)vis[to(x2+1,y2+1)][j][mid]++;
if(x2+1<=n) vis[to(x2+1,y1)][j][mid]--;
if(y2+1<=m) vis[to(x1,y2+1)][j][mid]--;
}
}
for(re int i=1;i<=n;i++)
for(re int j=1;j<=m;j++)
{
mid=to(i,j);
for(re int k=0;k<20;k++)
{
vis[mid][k][0]+=vis[to(i-1,j)][k][0]+vis[to(i,j-1)][k][0]-vis[to(i-1,j-1)][k][0];
vis[mid][k][1]+=vis[to(i-1,j)][k][1]+vis[to(i,j-1)][k][1]-vis[to(i-1,j-1)][k][1];
if(vis[mid][k][a[mid][k]^1]>0){ans++;break;}
}
}
printf("%d",ans);
return 0;
}
T2.蓝蓝的数列
题目大意:定义如下数列:
[a_n=left{egin{aligned}0~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ n=1\a_{lfloorfrac{n}{2}
floor}+(-1)^{frac{n(n+1)}{2}}~~~~nge 2end{aligned}
ight.
]
求(sum_{i=1}^{n}left|a_i ight|)的值。
考场上想了好久呀!(题目漏看了一个绝对值
现在还不会,以后再来补吧,咕咕咕~~
T3.树
题目大意:给出一棵树,求所有包含节点(x)的联通块的权值和,一个连通块的权值定义为这个联通块的所有点的权值之积。支持操作为修改权值和改变一个节点的父亲。
这题考场上毫无头绪,也是看题解才略懂一些。
这题也是一道DP题,(f[u][i])为以(u)为根的子树中,包含根节点的联通块大小为(i)的权值和。(
考虑到联通块大小最大为(10),所以我们可以更新每个点与其距离为(10)以内的祖先节点,每次递归计算答案。
先是操作一,修改权值,其实我们可以将这个点对其祖先节点的贡献归零,再重新计算新的贡献。操作二的方式差不多,删去这个点对其原祖先节点的贡献,再重新计算对现在祖先节点的贡献,由于联通块大小最多为(c),更新一个节点的(f)需要(O(c^2)),所以每次删除和计算的时间复杂度为(O(c^3))。
又由于联通块的权值定义为点的乘积,所以我们可以重新定义运算符,使其变成卷积的形式就可以大大减小代码量 (可蒟蒻的我只会看题解写
小结:
解题关键:想到如何快速计算贡献
芝士点:树形DP
手起,码落:
#include<bits/stdc++.h>
#define re register
#define mod 1000000007
using namespace std;
inline int read()
{
re int x=0,f=1;
re char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-')f*=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
inline int Pow(long long x,int y)
{
re long long sum=1;
for(;y;x=(x*x)%mod,y>>=1)
if(y&1)sum=(sum*x)%mod;
return sum;
}
const int N=200005;
struct node{long long val[11];}e[N];
node operator +(node a,node b)
{
for(re int i=9;i>0;i--)
for(re int j=1;j+i<=10;j++)
a.val[i+j]=(a.val[i+j]+a.val[i]*b.val[j])%mod;
return a;
}
node operator -(node a,node b)
{
for(re int i=1;i<=9;i++)
for(re int j=1;j+i<=10;j++)
a.val[i+j]=(a.val[i+j]+(mod-a.val[i])*b.val[j]%mod)%mod;
return a;
}
int n,m,fa[N];
long long a[N];
inline void dele(int x,int dep)
{
if(x==1||dep==0)return ;
dele(fa[x],dep-1);
e[fa[x]]=e[fa[x]]-e[x];
}
inline void add(int x,int dep)
{
if(x==1||dep==0)return ;
e[fa[x]]=e[fa[x]]+e[x];
add(fa[x],dep-1);
}
inline node query(int x,int y)
{
if(x==1||y==0)return e[x];
return e[x]+(query(fa[x],y-1)-e[x]);
}
int main()
{
n=read(),m=read();
for(re int i=1;i<=n;i++) a[i]=read(),e[i].val[1]=a[i];
for(re int i=2;i<=n;i++) fa[i]=read();
for(re int i=n;i>1;i--) e[fa[i]]=e[fa[i]]+e[i];
for(re int i=1,type,x,y;i<=m;i++)
{
type=read(),x=read(),y=read();
if(type==0)
{
dele(x,10);
for(re int j=1;j<=10;j++)
e[x].val[j]=e[x].val[j]*Pow(a[x],mod-2)%mod*y%mod;
a[x]=y,add(x,10);
}
else if(type==1)
{
dele(x,10),add(fa[x],9),fa[x]=y;
dele(fa[x],9),add(x,10);
}
else printf("%lld
",query(x,y).val[y]);
}
return 0;
}