Atcoder 233
Atcoder 233
\(D.\)Count Intervaul
题意:
给定一个长度为\(N\)的序列\(A=\{A_1,A_2,...,A_n\}\)和一个整数\(K\),问有多少个数对\((l,r)\),\(st.\) \(\sum_{i=l}^rA_i=K\)
\(1\le N\le2\times10^5\) \(-10^9\le A_i \le 10^9\) \(1\le K\le10^{18}\)
Sol
很容易想到前缀和,但求完前缀和之后如果暴力枚举左右端点\(O(n^2)\)无法承受
由于要求\(S[r]-s[l-1]=K\),转换一下,对于每个\(r\)求有多少个\(l\)满足要求,即求\(S[r]-k\)有多少个,用\(map\) 记录一下即可 \(O(nlogn)\)
#include <bits/stdc++.h>
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> void rd (T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
template <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=2e5+10;
int a[N];
ll s[N];
map<ll,ll>hs;
int main()
{
int n;
ll k;
rd(n),rd(k);
for(int i=1;i<=n;i++) rd(a[i]);
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
ll ans=0;
for(int i=1;i<=n;i++)
{
hs[s[i-1]]++;
ans+=hs[s[i]-k];
}
cout<<ans<<endl;
return 0;
}
\(F.\)Swap and Sort
题意
有一个关于\((1,2,...,N)\)的排列\(P=(P_1,P_2,...,P_n)\)
现在有\(M\)中操作:第\(i\)种操作是交换\(P\)的第\(a_i\)和第\(b_i\)个元素
问是否可以操作不大于\(5\times10^5\)次使得\(P\)变为\((1,2,...,N)\)
如果可以输出操作次数和操作顺序,如果不可以输出-1
\(1\le N \le5000\) \(1\le M\le min(2\times10^5,\frac{N(N-1)}{2})\)
\((a_i,b_i)!=(a_j,b_j)\) if \(i!=j\)
Sol
对于这种全排序序列的问题,很常见的一个想法就是构造连通图
对于所有种类的操作,将它们用并查集划分成一个个连通块
然后判断一遍\(P_i\)是否和\(i\)在一个连通块内,如果不在,说明\(P_i\)和\(i\)无法通过交换的到,输出\(-1\)
否则,说明有解,那么从一个没有被访问过且点度为\(1\)的数\(P_i\)开始复位并删点,如果他可以通过这些操作到达\(i\),说明这条寻找路径上的所有交换操作都可以使用,加入答案中即可,等价于在生成树上找。
由于最多交换\(999+998+...+2+1=499500\)次,所有有解的话一定满足条件
#include <bits/stdc++.h>
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> void rd (T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
template <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=1005;
int p[N],fa[N],st[N];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
struct edges{int v,id;};
vector<edges>e[N];
vector<int>ans;
bool dfs2(int u,int to,int f)
{
if(p[u]==to) return true;
for(auto t:e[u])
{
int v=t.v,id=t.id;
if(v==f) continue;
if(dfs2(v,to,u))
{
swap(p[u],p[v]);
ans.pb(id);
return true;
}
}
return false;
}
void dfs1(int u) //从点度为1的点开始搜
{
st[u]=1;
for(auto t:e[u])
{
int v=t.v;
if(st[v]) continue;
dfs1(v);
}
dfs2(u,u,-1);
}
int main()
{
int n;
rd(n);
for(int i=1;i<=n;i++) rd(p[i]),fa[i]=i;
int m;
rd(m);
for(int i=1;i<=m;i++)
{
int x,y;
rd(x),rd(y);
int fx=find(x),fy=find(y);
if(fx!=fy)
{
fa[fx]=fy;
e[x].pb({y,i});
e[y].pb({x,i});
}
}
for(int i=1;i<=n;i++)
{
int fx=find(p[i]),fy=find(i);
if(fx!=fy)
{
puts("-1");
return 0;
}
}
for(int i=1;i<=n;i++)
if(!st[i]) dfs1(i);
printf("%d\n",ans.size());
for(auto id:ans) printf("%d ",id);
return 0;
}
\(G.\)Strongest Takahashi
题意
给定一个\(N\times N\)的网格图,里面有若干个障碍#,每次可以选择一个整数\(D\)(\(1\le D\le N\)),然后选择一个\(D\times D\)的区域把里面的障碍都消掉,每次消耗为\(D\),请问最少需要消耗多少可以把障碍全部消完。
\(1\le N \le 50\)
Sol
定义\(dp[x_1][y_1][x_2][y_2]\)为消掉以(\(x_1,y_1\))为左上角,以(\((x_2,y_2)\))为右下角的矩形内的障碍最少需要的消耗。
一个\(A\times B\)的矩形的最大消耗为\(max(A,B)\)
可以考虑将一个矩形按划分方式递推
1.横着划分:\(dp[x_1][y_1][x_2][y_2]=min(dp[x_1,y_1,x_2,y_2],dp[x_1][y_1][k][y_2]+dp[k+1][y_1][x_2][y_2])\) \(x_1\le k \lt x_2\)
2.竖着划分:\(dp[x_1][y_1][x_2][y_2]=min(dp[x_1][y_1][x_2][y_2],dp[x_1][y_1][x_2][k]+dp[x_1][k+1][x_2][y_2])\) \(y_1 \le k \lt y_2\)
#include <bits/stdc++.h>
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> void rd (T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
template <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
char g[55][55];
int dp[55][55][55][55];
int dfs(int x1,int y1,int x2,int y2)
{
int &t=dp[x1][y1][x2][y2];
if(t!=-1) return t;
if(x1==x2&&y1==y2) return g[x1][y1]=='#';
t=max(x2-x1+1,y2-y1+1);
for(int k=x1;k<x2;k++)
t=min(t,dfs(x1,y1,k,y2)+dfs(k+1,y1,x2,y2));
for(int k=y1;k<y2;k++)
t=min(t,dfs(x1,y1,x2,k)+dfs(x1,k+1,x2,y2));
return t;
}
int main()
{
memset(dp,-1,sizeof dp);
int n;
rd(n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>g[i][j];
printf("%d\n",dfs(1,1,n,n));
return 0;
}
\(Ex.Manhattan Christmas Tree\)
题意
在二维平面上给定\(N\)个点,有\(Q\)个询问,每次询问给定一个点\((a_i,b_i)\)和\(K_i\),问这\(N\)个点距离\((a_i,b_i)\)第\(K_i\)近的距离是多少
距离的计算是曼哈顿距离.
所有数据的范围都是\(1\le data \le 10^5\)
Sol
前置知识:
假设\(A(x_1,y_1),B(x_2,y_2)\)
曼哈顿距离:\(d1=|x_1-x_2|+|y_1-y_2|\)
切比雪夫距离:\(d_2=max(|x_1-x_2|,|y_1-y_2|)\)
曼哈顿距离到切比雪夫距离:\((x,y)->(x+y,x-y)\) 可以看做把坐标系旋转\(45^{。}\)
推导:
(1) \(d_1=x_1-x_2+y_1-y_2\) (2) \(d_1=x_1-x_2+y_2-y_1\) (3) \(d_1=x_2-x_1+y_1-y_2\) (4) \(d_1=x_2-x_1+y_2-y_1\)
求这4中情况中最大的
其中(1)(4)得到\(|(x_1+y_1)-(x_2+y_2)|\) (2)(3)得到\(|(x_1-y_1)-(x_2-y_2)|\)
最大就是\(max(|(x_1+y_1)-(x_2+y_2)|,|(x_1-y_1)-(x_2-y_2)|)\)
最小值同理
切比雪夫距离到曼哈顿距离转化:\((x,y)->(\frac{x+y}{2},\frac{x-y}{2})\)
于是对每个询问就可以二分一下第\(k\)近的距离,\(check\)就是查询有多少个点到\((a_i,b_i)\)的切比雪夫距离\(\le\)二分距离\(mid\),这就是一个二维数点问题,可以用树状数组或者主席树解决。这里采用树状数组。
#include <bits/stdc++.h>
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> void rd (T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
template <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=2e5+10;
struct Point{
int x,y;
bool operator < (const Point &t) const
{
return y<t.y;
}
}p[N];
vector<int>tr[N];
int lowbit(int x){return x&-x;}
void add(int x,int y)
{
for(int pos=x;pos<N;pos+=lowbit(pos))
tr[pos].pb(y);
}
int sum(int x,int y1,int y2)
{
int res=0;
for(int pos=x;pos;pos-=lowbit(pos))
{
res+=upper_bound(tr[pos].begin(),tr[pos].end(),y2)-lower_bound(tr[pos].begin(),tr[pos].end(),y1);
}
return res;
}
int query(int x1,int x2,int y1,int y2)
{
//边界特判
x1=max(x1,1);
x2=max(x2,0);
x1=min(x1,N-1);
x2=min(x2,N-1);
return sum(x2,y1,y2)-sum(x1-1,y1,y2);
}
bool check(int a,int b,int k,int mid) //二分距离,如果在这个范围内的点数大于k说明距离大了
{
int x=a+b+1;
int y=a-b+1;
return query(x-mid,x+mid,y-mid,y+mid)>=k;
}
int n,t;
int main()
{
rd(n);
for(int i=1;i<=n;i++)
{
int x,y;
rd(x),rd(y);
p[i].x=x+y+1,p[i].y=x-y+1;
}
sort(p+1,p+1+n);
for(int i=1;i<=n;i++)
add(p[i].x,p[i].y);
rd(t);
while(t--)
{
int a,b,k;
rd(a),rd(b),rd(k);
int l=0,r=2e5+10;
while(l<r)
{
int mid=l+r>>1;
if(check(a,b,k,mid)) r=mid;
else l=mid+1;
}
cout<<r<<'\n';
}
return 0;
}