题目为hyy大佬原创。
以下代码默认开 O 2 \ O2 O2
显然,这是一个图论最短路的题目,建图之后 d i j s t r a \ dijstra dijstra即可解决。
#include
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,m,w,h;
int a[100100],len=0,hea[100100];
struct node
{
int ne,to,l,fr;
}e[1600800];
inline void adde(int u,int v,int l)
{
e[++len].ne=hea[u];
hea[u]=len;
e[len].to=v;
e[len].l=l;;
}
struct noee
{
int u;
long long d;
bool operator <(const noee& rhs) const
{
return d>rhs.d;
}
};
int dis[100100];
priority_queue<noee> wq;
void dij(int s)
{
memset(dis,63,sizeof(dis));
dis[s]=0;
wq.push((noee){
s,0});
while(!wq.empty())
{
noee fr=wq.top();
wq.pop();
int u=fr.u;
long long d=fr.d;
int i;
if(d!=dis[u]) continue;
i=hea[u];
while(i)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].l)
{
dis[v]=dis[u]+e[i].l;
wq.push((noee){
v,dis[v]});
}
i=e[i].ne;
}
}
}
int main()
{
n=read();
int i=1;
while(i<=n)
{
scanf("%d",&a[i]);
if(i+a[i]<=n) adde(i,i+a[i],2);
if(i^n) adde(i,i+1,1);
if(i^1) adde(i,i-1,2);
++i;
}
dij(1);
printf("%d\n",dis[n]);
return 0;
}
覆盖整个区间,问最少使用数,这显然是可以用 d p \ dp dp来写的。
我们设 t o i \ to_{i} toi,表示以 i \ i i为右端点的所有区间中,最小的左端点;设 f i \ f_{i} fi表示 [ 1 , i ] \ [1,i] [1,i]都被覆盖的最小使用数。我们可以得到以下方程:
f i = ( min j = t o i − 1 i − 1 f j ) + 1 f_{i}= (\min_{j=to_{i}-1}^{i-1} f_{j}) + 1 fi=(j=toi−1mini−1fj)+1
可以证明这是正确的。
唯一的问题是这么做的复杂度是 O ( n 2 ) \ O(n^{2}) O(n2)的,不过很多数据结构都支持求区间求最小值,这里我使用的是线段树。
#include
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,to[1000100],minn[4000400],m;
inline void build(int l,int r,int k)
{
if(l>r) return ;
if(l==r)
{
if(l==0) minn[k]=0;
else minn[k]=999999999;
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
minn[k]=min(minn[k<<1],minn[k<<1|1]);
}
inline int query(int l,int r,int k,int ll,int rr)
{
if(l>r) return 999999999;
if((l>=ll)&&(r<=rr)) return minn[k];
if((l>rr)||(r<ll)) return 999999999;
int mid=(l+r)>>1;
return min(query(l,mid,k<<1,ll,rr),query(mid+1,r,k<<1|1,ll,rr));
}
inline void change(int l,int r,int k,int x,int val)
{
if(l>r) return ;
if(l==r)
{
minn[k]=val;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,k<<1,x,val);
else change(mid+1,r,k<<1|1,x,val);
minn[k]=min(minn[k<<1],minn[k<<1|1]);
}
int main()
{
memset(to,63,sizeof(to));
n=read();
m=read();
int i=1;
while(i<=m)
{
int l,r;
l=read();
r=read();
--l;
to[r]=min(to[r],l);
++i;
}
build(0,n,1);
i=1;
while(i<=n)
{
int f=query(0,n,1,to[i],i-1);
change(0,n,1,i,f+1);
++i;
}
int ans=query(0,n,1,n,n);
if(ans>1000010) printf("-1\n");
else printf("%d\n",ans);
return 0;
}
当然
这种写法虽然无脑,但是确实不怎么巧妙。不过稍加思索之后,它可以转化为最短路的模型。
每一个区间 [ l , r ] \ [l,r] [l,r]都建一条边 l − 1 → r \ l-1 \rightarrow r l−1→r,但是这样的话,怎么允许区间的覆盖呢?很简单,每个节点 i \ i i都向 i − 1 \ i-1 i−1建一条边即可。
#include
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,m,w,h;
int len=0,hea[1001001];
struct node
{
int ne,to,l,fr;
}e[2002002];
inline void adde(int u,int v,int l)
{
e[++len].ne=hea[u];
hea[u]=len;
e[len].to=v;
e[len].l=l;;
}
struct noee
{
int u;
long long d;
bool operator <(const noee& rhs) const
{
return d>rhs.d;
}
};
int dis[1001001];
priority_queue<noee> wq;
void dij(int s)
{
memset(dis,63,sizeof(dis));
dis[s]=0;
wq.push((noee){
s,0});
while(!wq.empty())
{
noee fr=wq.top();
wq.pop();
int u=fr.u;
long long d=fr.d;
int i;
if(d!=dis[u]) continue;
i=hea[u];
while(i)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].l)
{
dis[v]=dis[u]+e[i].l;
wq.push((noee){
v,dis[v]});
}
i=e[i].ne;
}
}
}
int main()
{
n=read();
m=read();
int i=1,l,r;
while(i<=m)
{
l=read();
r=read();
adde(l-1,r,1);
++i;
}
i=1;
while(i<=n)
{
adde(i,i-1,0);
++i;
}
dij(0);
if(dis[n]<999999999) printf("%d\n",dis[n]);
else printf("-1\n");
return 0;
}
显然是一个五维的完全背包,也许有大佬可以使用状态压缩,但实际上裸的背包也可以过这道题。
#include
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
int f[21][21][21][21][21],m,a[21],b[21],c[21],d[21],e[21],aa,bb,cc,dd,ee,g[21];
int main()
{
aa=read();
bb=read();
cc=read();
dd=read();
ee=read();
m=read();
int i=1;
while(i<=m)
{
a[i]=read();
b[i]=read();
c[i]=read();
d[i]=read();
e[i]=read();
g[i]=read();
++i;
}
memset(f,0,sizeof(f));
int aaa=1,bbb=1,ccc=1,ddd=1,eee=1;
i=1;
while(i<=m)
{
aaa=a[i];
while(aaa<=aa)
{
bbb=b[i];
while(bbb<=bb)
{
ccc=c[i];
while(ccc<=cc)
{
ddd=d[i];
while(ddd<=dd)
{
eee=e[i];
while(eee<=ee)
{
f[aaa][bbb][ccc][ddd][eee]=max(f[aaa][bbb][ccc][ddd][eee],f[aaa-a[i]][bbb-b[i]][ccc-c[i]][ddd-d[i]][eee-e[i]]+g[i]);
++eee;
}
++ddd;
}
++ccc;
}
++bbb;
}
++aaa;
}
++i;
}
int ans=f[aa][bb][cc][dd][ee];
aaa=aa;
bbb=bb;
ccc=cc;
ddd=dd;
eee=ee;
while(1)
{
if(aa==0) break;
if(f[aa-1][bb][cc][dd][ee]^ans) break;
--aa;
}
while(1)
{
if(bb==0) break;
if(f[aa][bb-1][cc][dd][ee]^ans) break;
--bb;
}
while(1)
{
if(cc==0) break;
if(f[aa][bb][cc-1][dd][ee]^ans) break;
--cc;
}
while(1)
{
if(dd==0) break;
if(f[aa][bb][cc][dd-1][ee]^ans) break;
--dd;
}
while(1)
{
if(ee==0) break;
if(f[aa][bb][cc][dd][ee-1]^ans) break;
--ee;
}
printf("%d\n%d %d %d %d %d\n",ans,aaa-aa,bbb-bb,ccc-cc,ddd-dd,eee-ee);
return 0;
}
一道还算不错的 d p \ dp dp题。
如果不看保质期,这道题就是个完全背包。因为每天吃的食物是互不影响的,所以只需要每天取最小的可以让小鱼吃饱的钱就行了。
而加上保质期,这个问题就变成了了如何将完全背包中的某个物品删除。这个问题无疑是非常困难的。所以不如倒着来,从最后一天开始,一个个将物品加入完全背包。
#include
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,v,day=0,f[5050],val[3000300],mon,cost=0;
struct nobe
{
int c,t,d;
}a[5050];
inline bool mmp(nobe a,nobe b)
{
return a.t>b.t;
}
int main()
{
mon=read();
v=read();
n=read();
int i=1,j=n;
while(i<=n)
{
a[i].c=read();
a[i].t=read();
a[i].d=read();
day=max(day,a[i].t);
++i;
}
sort(a+1,a+n+1,mmp);
memset(f,63,sizeof(f));
f[0]=0;
i=day;
int now=1;
while(i>=1)
{
while((a[now].t>=i)&&(now<=n))
{
j=0;
while(j<=v+1)
{
if(f[j]<=9999999) f[min(v+1,j+a[now].d)]=min(f[min(v+1,j+a[now].d)],f[j]+a[now].c);
++j;
}
++now;
}
val[i]=min(f[v+1],f[v]);
--i;
}
i=1;
while(i<=day)
{
if(cost+val[i]<=mon) cost+=val[i];
else break;
++i;
}
printf("%d %d\n",i-1,mon-cost);
return 0;
}
由于 T ≤ 1000 \ T \leq 1000 T≤1000,所以每一行建一棵线段树就可以了。
如果将 T \ T T变为 50000 \ 50000 50000,二维线段树可以解决一切问题。
#include
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
int maxx[4040][1010],laz[4040][1010],c[1010][1010],n,m,tt;
inline void build(int l,int r,int k,int i)
{
if(l>r) return ;
laz[k][i]=0;
if(l==r)
{
maxx[k][i]=c[l][i];
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1,i);
build(mid+1,r,k<<1|1,i);
maxx[k][i]=max(maxx[k<<1][i],maxx[k<<1|1][i]);
}
inline void pushdown(int k,int i)
{
laz[k<<1][i]+=laz[k][i];
laz[k<<1|1][i]+=laz[k][i];
maxx[k<<1][i]+=laz[k][i];
maxx[k<<1|1][i]+=laz[k][i];
laz[k][i]=0;
}
inline void update(int l,int r,int k,int x,int ll,int rr,int i)
{
if((l>=ll)&&(r<=rr))
{
laz[k][i]+=x;
maxx[k][i]+=x;
return ;
}
if((l>rr)||(r<ll)) return ;
int mid=(l+r)>>1;
pushdown(k,i);
update(l,mid,k<<1,x,ll,rr,i);
update(mid+1,r,k<<1|1,x,ll,rr,i);
maxx[k][i]=-2147483648;
maxx[k][i]=max(maxx[k<<1][i],maxx[k<<1|1][i]);
}
inline int query(int l,int r,int k,int ll,int rr,int i)
{
if(l>r) return -2147483648;
if((l>=ll)&&(r<=rr)) return maxx[k][i];
if((l>rr)||(r<ll)) return -2147483648;
int mid=(l+r)>>1;
pushdown(k,i);
maxx[k][i]=max(maxx[k<<1][i],maxx[k<<1|1][i]);
return max(query(l,mid,k<<1,ll,rr,i),query(mid+1,r,k<<1|1,ll,rr,i));
}
int main()
{
n=read();
m=read();
register int i=1,j=1,k=1,l=1,q=1,x,ii=1,jj=1;
i=1;
while(i<=n)
{
j=1;
while(j<=m)
{
c[i][j]=read();
++j;
}
++i;
}
i=1;
while(i<=m)
{
build(1,n,1,i);
++i;
}
tt=read();
while(tt--)
{
q=read();
i=read();
j=read();
k=read();
l=read();
if(q==1)
{
x=read();
ii=j;
while(ii<=l)
{
update(1,n,1,x,i,k,ii);
++ii;
}
}
else
{
int ans=-2147483648;
ii=j;
while(ii<=l)
{
ans=max(ans,query(1,n,1,i,k,ii));
++ii;
}
printf("%d\n",ans);
}
}
return 0;
}
乍一看很蒙,但是我们细细想一想就不是很蒙了。
我们先对一个二进制数考虑:
A = 10101 x x x x x x , A < 10101100000 A=10101xxxxxx,A < 10101100000 A=10101xxxxxx,A<10101100000
x \ x x是不确定的数字,有 n u m \ num num位,已经确定的 1 \ 1 1有 s u m \ sum sum个,从左往右第一个 x \ x x恒为 0 \ 0 0。
因为要计算所有的1的总和,我们先考虑后面未知的部分。如果设某一位为 1 \ 1 1,其他的部分有 2 n u m − 2 \ 2^{num-2} 2num−2种可能,所以这一部分的总和为 ( n u m − 1 ) ⋅ 2 n u m − 2 \ (num-1) \cdot 2^{num-2} (num−1)⋅2num−2;接着考虑前面的部分。未知的部分可能性有 2 n u m − 1 2^{num-1} 2num−1种,所以是 s u m ⋅ 2 n u m − 1 \ sum \cdot 2^{num-1} sum⋅2num−1。
现在得出结论,设 n \ n n的二进制写法中 1 \ 1 1的写法为 n = s t o t s t o t − 1 s t o t − 2 ⋯ s 3 s 2 s 1 , s i ∈ { 0 , 1 } \ n=s_{tot}s_{tot-1}s_{tot-2} \cdots s_{3}s_{2}s_{1},s_{i} \in \{ 0,1 \} n=stotstot−1stot−2⋯s3s2s1,si∈{ 0,1}。
答案为:
( ∑ i = 1 t o t s i ) + ∑ i = 1 t o t { s i ⋅ [ ( ∑ j = i + 1 t o t s j ) 2 i − 1 + ( i − 1 ) 2 i − 2 ] } (\sum_{i=1}^{tot}s_{i}) + \sum_{i=1}^{tot} \{ s_{i} \cdot [(\sum_{j=i+1}^{tot} s_{j})2^{i-1} + (i-1) 2^{i-2}] \} (i=1∑totsi)+i=1∑tot{ si⋅[(j=i+1∑totsj)2i−1+(i−1)2i−2]}
和刚才的考虑方法类似,但是只用考虑后面未知的部分
A = 10101 x x x x x x , A < 10101100000 A=10101xxxxxx,A < 10101100000 A=10101xxxxxx,A<10101100000
x \ x x是不确定的数字,有 n u m \ num num位,已经确定的 1 \ 1 1有 s u m \ sum sum个,从左往右第一个 x \ x x恒为 0 \ 0 0。
当有 j \ j j个不确定的数字为 1 \ 1 1时,所有的情况有 ( s u m − 1 j ) \ \binom{sum-1}{j} (jsum−1)种,显然这一部分的结果为 ( j + s u m ) ( s u m − 1 j ) \ (j+sum)^{\binom{sum-1}{j}} (j+sum)(jsum−1),即可得出结论。
设 n \ n n的二进制写法中 1 \ 1 1的写法为 n = s t o t s t o t − 1 s t o t − 2 ⋯ s 3 s 2 s 1 , s i ∈ { 0 , 1 } \ n=s_{tot}s_{tot-1}s_{tot-2} \cdots s_{3}s_{2}s_{1},s_{i} \in \{ 0,1 \} n=stotstot−1stot−2⋯s3s2s1,si∈{ 0,1}。
答案为:
( ∑ i = 1 t o t s i ) ⋅ ∏ i = 1 t o t [ s i ⋅ ∏ j = 0 j < i ( j + s u m ) ( s u m − 1 j ) ] (\sum_{i=1}^{tot}s_{i}) \cdot \prod_{i=1}^{tot} [ s_{i} \cdot \prod_{j=0}^{j<i} (j+sum)^{\binom{sum-1}{j}} ] (i=1∑totsi)⋅i=1∏tot[si⋅j=0∏j<i(j+sum)(jsum−1)]
通过一些优化可以减少一些复杂度。
#include
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int mod=100000007;
int ttt,c;
long long n,ans=0,f[70][70],cc[130];
long long pow2[70];
inline int bitcount(long long n)
{
register int c=0;
while(n)
{
n&=(n-1);
++c;
}
return c;
}
inline long long ppow(long long a,int b)
{
long long res=1;
while(b)
{
if(b&1) res=(res*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return res%mod;
}
signed main()
{
register int i=1,j,sum;
pow2[0]=1;
while(i<=52)
{
pow2[i]=pow2[i-1]*2ll;
++i;
}
f[0][0]=1;
i=1;
while(i<=52)
{
f[i][0]=f[i][i]=1;
j=1;
while(j<i)
{
f[i][j]=f[i-1][j]+f[i-1][j-1];
if(f[i][j]>=(mod-1)) f[i][j]-=mod-1;
++j;
}
++i;
}
ttt=read();
while(ttt--)
{
c=read();
scanf("%lld",&n);
if(c==1)
{
ans=0;
i=51;
while((i!=0)&&((n>>(i-1))&1)==0) --i;
sum=0;
while(i)
{
ans+=pow2[i-1]*sum;
ans%=mod;
if(i^1) ans+=pow2[i-2]*(i-1);
ans%=mod;
++sum;
--i;
while((i>0)&&((n>>(i-1))&1)==0) --i;
}
ans+=bitcount(n);
if(ans>=mod) ans-=mod;
printf("%lld\n",ans);
}
else
{
ans=1;
i=51;
memset(cc,0,sizeof(cc));
while((i)&&((n>>(i-1))&1)==0) --i;
sum=0;
while(i)
{
j=(sum==0) ? 1 : 0;
while(j<i)
{
cc[j+sum]+=f[i-1][j];
cc[j+sum]%=(mod-1);
++j;
}
++sum;
--i;
while((i)&&((n>>(i-1))&1)==0) --i;
}
i=1;
while(cc[i])
{
ans*=ppow(i,cc[i]);
ans%=mod;
++i;
}
ans*=bitcount(n);
ans%=mod;
printf("%lld\n",ans);
}
}
return 0;
}
展开这个式子,就会得到这样一个形式的东西:
f n = b + a b + a 2 b + a 3 b + ⋯ + a n − 2 b + a n − 1 f_{n}=b+ab+a^{2}b+a^{3}b+ \cdots +a^{n-2}b+a^{n-1} fn=b+ab+a2b+a3b+⋯+an−2b+an−1
是一个等差数列与一个幂的和,这两个都有 O ( log n ) \ O( \log n) O(logn)的做法。
设 s u m p , c = 1 + p + p 2 + ⋯ + p c \ sum_{p,c}=1+p+p^{2}+ \cdots +p^{c} sump,c=1+p+p2+⋯+pc,有
s u m p , c = { 1 , c = = 0 p + 1 c = = 1 ( 1 + p c 2 ) s u m p , c 2 − 1 + p c o t h e r sum_{p,c}= \begin{array}{rcl} \begin{cases} 1, & & c==0\\ p + 1 & & c==1\\ (1+p^{\frac{c}{2}})sum_{p,\frac{c}{2}-1}+p^{c} & & other \end{cases} \end{array} sump,c=⎩⎪⎨⎪⎧1,p+1(1+p2c)sump,2c−1+pcc==0c==1other
证明略
#include
#include
#include
using namespace std;
#define int long long
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
int m,bb;
int a,b,mod,n;
long long qpow(long long a,long long b)
{
long long r=1,base=a;
while(b)
{
if(b&1) r=(r*base)%mod;
base=(base*base)%mod;
b>>=1;
}
return r;
}
long long sum(long long p,long long c)
{
if(c==0) return 1ll;
if(c==1) return p%mod+1ll;
if(c&1) return ((1ll+qpow(p,(c+1ll)/2ll))*sum(p,(c-1ll)/2ll))%mod;
else return ((1ll+qpow(p,c/2))*sum(p,c/2ll-1ll)+qpow(p,c))%mod;
}
signed main()
{
int a,b;
a=read();
b=read();
mod=read();
n=read();
if(n==1)
{
printf("1\n");
return 0;
}
int res=1;
res*=sum(a,n-2)%mod;
res%=mod;
res*=b;
res%=mod;
res+=qpow(a,n-1);
res%=mod;
cout<<res<<endl;
return 0;
}
S T L \ STL STL在 O 2 \ O2 O2下巨快。
l c p \ lcp lcp? k m p \ kmp kmp啊!
#include
using namespace std;
int m,next[5050];
string s,s2;
void getnext(string p)
{
int plen=p.length();
next[0]=-1;
int k=-1;
int j=0;
while(j<plen-1)
{
if((k==-1)||(p[j]==p[k]))
{
++j;
++k;
if(p[j]!=p[k]) next[j]=k;
else next[j]=next[k];
}
else
{
k=next[k];
}
}
}
int kmpsearch(string s, string p)
{
int i=0;
int j=0;
int slen=s.length();
int plen=p.length();
while((i<slen)&&(j<plen))
{
if((j==-1)||(s[i]==p[j]))
{
++i;
++j;
}
else j=next[j];
}
if(j==plen) return i-j;
else return -1;
}
int main()
{
scanf("%d",&m);
cin>>s;
int cz,p,len;
while(m--)
{
scanf("%d",&cz);
if(cz==4) cout<<s<<endl;
else if(cz==2)
{
scanf("%d%d",&p,&len);
s.erase(p-1,len);
}
else if(cz==1)
{
scanf("%d",&p);
cin>>s2;
s.insert(p,s2);
}
else
{
cin>>s2;
getnext(s2);
if(kmpsearch(s,s2)!=-1) printf("yes\n");
else printf("no\n");
}
}
return 0;
}
先考虑 m = 1 \ m=1 m=1的情况, a i \ a_{i} ai表示第 i \ i i个数字。这时矩阵变成区间。
首先:
正确性是显然的。这两条使我们可以用线段树维护。
m > 1 \ m>1 m>1时呢?容易想到建 m \ m m棵线段树,但是这样复杂度是过不去的 (我卡过9九点)。实际上状态压缩,用二进制表示就可以了。
#include
using namespace std;
char buf[1<<20],*fs,*ft;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';ch=getchar();}
return s*w;}
long long is[400001],maxl,a[100001];
int n,m,q;
inline void pushup(int l,int r,int k)
{
int mid=(l+r)>>1;
long long aa=a[mid]^a[mid+1],ss=is[k<<1]&is[k<<1|1];
is[k]=aa&ss;
}
inline void build(int l,int r,int k)
{
if(l==r)
{
is[k]=(1ll<<(m))-1ll;
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
pushup(l,r,k);
}
inline void change(int l,int r,int k,int x)
{
if(l==r) return ;
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,k<<1,x);
else change(mid+1,r,k<<1|1,x);
pushup(l,r,k);
}
inline long long query(int l,int r,int k,int ll,int rr,int x,int len)
{
if((ll<=l)&&(rr>=r))
{
return ((is[k]>>(x-1))&((1ll<<(len))-1));
}
if((r<ll)||(l>rr)) return (1ll<<(len))-1;
int mid=(l+r)>>1;
long long f1=query(l,mid,k<<1,ll,rr,x,len),f2=query(mid+1,r,k<<1|1,ll,rr,x,len),aa,aa1,aa2;
if(mid+1>rr) return f1;
if(mid<ll) return f2;
aa1=((a[mid]>>(x-1))&((1ll<<(len))-1ll));
aa2=((a[mid+1]>>(x-1))&((1ll<<(len))-1ll));
aa=aa1^aa2;
return ((f1&f2)&aa);
}
int main()
{
n=read();
m=read();
q=read();
maxl=(1ll<<(m+1))-1;
register int i=1,j=1,k,l,cz,ii;
bool f;
while(i<=n)
{
j=1;
while(j<=m)
{
a[i]|=(1ll*read())<<(j-1);
++j;
}
++i;
}
build(1,n,1);
while(q--)
{
cz=read();
if(cz)
{
i=read();
j=read();
k=read();
l=read();
ii=j;
if(query(1,n,1,i,k,j,l-j+1)) printf("1\n");
else printf("0\n");
}
else
{
i=read();
j=1;
a[i]=0;
while(j<=m)
{
a[i]|=(1ll*read())<<(j-1);
++j;
}
change(1,n,1,i);
}
}
return 0;
}
orzhyy
比赛的时候第六题没有优化,结果没有 A \ A A
个人觉得这一堆题还是不错的,不过我太菜了就是。