T1:地图
题目大意:给出一个 n × n n\times n n×n的01矩阵,其中 a 1 , 1 = 1 a_{1,1}=1 a1,1=1, a 1 , i a_{1,i} a1,i表示第 i i i列和第 n n n列所有元素的异或值(不包括 a 1 , i a_{1,i} a1,i), a i , 1 a_{i,1} ai,1表示第 i i i行和第 n n n行所有元素的异或值(不包括 a i , 1 a_{i,1} ai,1),现在更改这个矩阵的里的一个数,问更改的数是哪个。 n ≤ 2000 n\le 2000 n≤2000
题解:
如果这个矩阵有元素进行改动,那么可以知道 a 1 , i a_{1,i} a1,i和 a i , 1 a_{i,1} ai,1可能会有元素有问题
那么可以将这个矩阵的有问题的 a 1 , i a_{1,i} a1,i和 a i , 1 a_{i,1} ai,1找到,然后分类讨论
- 行和列上都没有问题:那么显然更改了 a 1 , 1 a_{1,1} a1,1
- 行和列上都只有一个元素有问题:行和列的交点就是更改的元素
- 行上全部有问题、列上只有一个有问题(反之亦然):行上都有问题说明在更改的数在第 n n n行,那么对应的哪列有问题更改的数就在哪列
- 行和列上的元素本身有问题
- 只有行有问题(或只有列):那么就是 a n , 1 a_{n,1} an,1(或者 a 1 , n a_{1,n} a1,n)
- 行和列都是全部有问题: a n , n a_{n,n} an,n
Code
#include<cstdio>
#define N 2005
using namespace std;
int n,num,rnum,cnum,a[N][N],c[N],r[N],err[N*N][3],err_r[N],err_c[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;++i)
{
for (int j=1;j<=n;++j)
{
scanf("%d",&a[i][j]);
if (j!=1) c[i]^=a[i][j];
if (i!=1) r[j]^=a[i][j];
}
}
c[n]^=a[n][1];
r[n]^=a[1][n];
if (a[1][1]==1)
{
printf("1 1\n");
return 0;
}
for (int i=2;i<n;++i)
{
if (c[i]^c[n]!=a[i][1]) ++cnum,err_c[cnum]=i,++num,err[num][1]=i,err[num][2]=1;
if (r[i]^r[n]!=a[1][i]) ++rnum,err_r[rnum]=i,++num,err[num][1]=1,err[num][2]=i;
}
if (num==1)
{
printf("%d %d\n",err[1][1],err[1][2]);
return 0;
}
if (num==2)
{
if (rnum==1&&cnum==1)
{
printf("%d %d\n",err_c[cnum],err_r[rnum]);
return 0;
}
}
if (num==n-2)
{
if (cnum==n-2) printf("%d 1\n",n);
else printf("1 %d\n",n);
return 0;
}
if (num==n-1)
{
if (cnum==1) printf("%d %d\n",err_c[cnum],n);
else printf("%d %d\n",n,err_r[rnum]);
}
if (num==2*(n-2))
{
printf("%d %d\n",n,n);
return 0;
}
return 0;
}
T2:灌水
题目大意:有一宽度为 n n n的水槽,水槽两边无限高,每个单位长度都有一定的高度,水会往左右扩散至第一个比自己高度高的位置。 q q q次操作,每次将第 x x x位的水灌到 y y y(操作之间没有影响),问水会扩散到多少个格子。 n , q ≤ 2 × 1 0 5 n,q\le2\times10^5 n,q≤2×105。
题解:
想到一种显然的暴力就是从当前位置往左右枚举,直到找到一个比自己高的位置,时间复杂度 O ( q n ) O(qn) O(qn)
考虑优化,发现可以不用枚举,可以二分,同时维护一下区间最大值即可。
维护区间最大值建议使用ST表,时间复杂度 O ( 1 ) O(1) O(1)查询最大值,总的时间复杂度 O ( q log n ) O(q\log n) O(qlogn)
如果用线段树时间复杂度会到 O ( q log 2 n ) O(q\log^2n) O(qlog2n),会被卡
Code
#include<cmath>
#include<cstdio>
#include<algorithm>
#define inf 2147483647
#define N 500005
#define ll long long
using namespace std;
int n,q,x,l,r,mid,res,L,R;
ll y,a[N],sum[N],f[N][20],lg[N];
int query(int l,int r)
{
int x=lg[r-l+1];
return max(f[l][x],f[r-(1<<x)+1][x]);
}
int main()
{
scanf("%d%d",&n,&q);
for (int i=1;i<=n;++i)
scanf("%lld",&a[i]),f[i][0]=a[i];
for (int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
for (int j=1;j<=18;++j)
for (int i=1;i<=n;++i)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for (int i=1;i<=n;++i)
sum[i]=sum[i-1]+a[i];
a[0]=a[n+1]=inf;
for (int i=1;i<=q;++i)
{
scanf("%d%lld",&x,&y);
l=1;r=x-1;
res=0;
while (l<=r)
{
mid=(l+r)>>1;
if (query(mid,r)>=y) res=mid,l=mid+1;
else r=mid-1;
}
L=res;
l=x+1;r=n;
res=n+1;
while (l<=r)
{
mid=(l+r)>>1;
if (query(l,mid)>=y) res=mid,r=mid-1;
else l=mid+1;
}
R=res;
printf("%lld\n",(R-L-1)*y-(sum[R-1]-sum[L]));
}
return 0;
}
T3:配对
题目大意:给出 n n n个数( n n n是偶数),现在要删去一些数,让剩下的数两两配对后和在 [ l , r ] [l,r] [l,r],问最少删去多少个数(必须删去偶数个,如果全删完了也满足条件,可以不删)。 n ≤ 1 0 6 n\le 10^6 n≤106
题解:
先将序列排序
发现如果 ( a , c ) ( b , d ) (a,c)(b,d) (a,c)(b,d)是合法的,那么 ( a , d ) ( b , c ) (a,d)(b,c) (a,d)(b,c)也是合法的( a ≤ b ≤ c ≤ d a\le b\le c\le d a≤b≤c≤d)
略证:
因为 ( a , c ) (a,c) (a,c)合法,那么 l ≤ a + c ≤ r l\le a+c\le r l≤a+c≤r
同理 l ≤ b + d ≤ r l\le b+d\le r l≤b+d≤r
那么 l ≤ a + c ≤ a + d ≤ b + d ≤ r l\le a+c\le a+d\le b+d\le r l≤a+c≤a+d≤b+d≤r
l ≤ a + c ≤ b + c ≤ b + d ≤ r l\le a+c\le b+c\le b+d\le r l≤a+c≤b+c≤b+d≤r
所以 ( a , d ) ( b , c ) (a,d)(b,c) (a,d)(b,c)合法
那么可以思考一种贪心
用两个指针分别指到头尾(设头元素是 x x x,尾元素是 y y y)
如果 x + y > r x+y>r x+y>r,那么 y y y已经无法造成合法贡献了,可以直接删除
同理如果 x + y < l x+y<l x+y<l, x x x也不会造成任何贡献
如果 l ≤ x + y ≤ r l\le x+y\le r l≤x+y≤r,那么就可以把 x , y x,y x,y配对,然后往下走
最后注意判断如果删掉了奇数那就再删一个
Code
#include<cstdio>
#include<algorithm>
#define N 1000005
using namespace std;
int n,l,r,ans,a[N];
int main()
{
scanf("%d%d%d",&n,&l,&r);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
int i=1,j=n;
while (i<j)
{
if (a[i]+a[j]>r) --j,++ans;
else if (a[i]+a[j]<l) ++i,++ans;
else if (a[i]+a[j]>=l&&a[i]+a[j]<=r) ++i,--j;
}
if (i==j) ++ans;
printf("%d\n",ans);
return 0;
}
T4:旅行
题目大意:给出一张 n n n点 m m m边的图, q q q次询问,每次询问要求从 x x x开始,只能经过边权 ≤ w \le w ≤w的边,问最多能到达多少个点。 n , q ≤ 2 × 1 0 5 , m ≤ 4 × 1 0 5 n,q\le2\times10^5,m\le4\times10^5 n,q≤2×105,m≤4×105
题解:
其实运用了一个显而易见的结论将题目简化了。
由于询问之间互相独立,考虑离线,发现会走的边只会在最小生成树上,那么将询问的 w w w和每条边的边权从小到大排序,每次询问将所有 ≤ w \le w ≤w的边加入,维护并查集的同时记录连通块大小即可
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
struct node
{
int from,to,val;
}a[N<<1];
struct ques
{
int id,val,pos;
}c[N];
int n,m,q,x,y,z,f[N],size[N],ans[N];
bool cmpm(node x,node y) {return x.val<y.val;}
bool cmpq(ques x,ques y) {return x.val<y.val;}
int find(int x)
{
if (f[x]!=x) f[x]=find(f[x]);
return f[x];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
f[i]=i,size[i]=1;
for (int i=1;i<=m;++i)
scanf("%d%d%d",&a[i].from,&a[i].to,&a[i].val);
sort(a+1,a+m+1,cmpm);
scanf("%d",&q);
for (int i=1;i<=q;++i)
scanf("%d%d",&c[i].pos,&c[i].val),c[i].id=i;
sort(c+1,c+q+1,cmpq);
int i=1,j=1;
while (j<=q)
{
while (a[i].val<=c[j].val&&i<=m)
{
int xx=find(a[i].from),yy=find(a[i].to);
if (xx!=yy)
{
f[xx]=yy;
size[yy]+=size[xx];
}
++i;
}
int xx=find(c[j].pos);
ans[c[j].id]=size[xx];
++j;
}
for (int i=1;i<=q;++i)
printf("%d\n",ans[i]);
return 0;
}