csoj寒假训练10
A
并查集
两个黑球之间距离不够这个白球通过的话,视为一个集合
考虑怎样维护这样两两之间的关系,我们使用并查集
同时黑球与直线的关系也要做一次维护
最后可以直接判断是否上下两条直线是否在一个集合里面
如果在一个集合里面说明没有一条容许白球通过的通路
#include<iostream>
#include<stdio.h>
#define int long long
using namespace std;
const int N=5050;
int R,n,p[N],x[N],y[N],r[N],a,b;
int find(int x){
if(p[x]==x)return x;
return p[x]=find(p[x]);
}
int cal(int i,int j){
int dx=abs(x[i]-x[j]);
int dy=abs(y[i]-y[j]);
return dx*dx+dy*dy;
}
void merge(int i,int j){
p[find(i)]=find(j);
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// freopen("15.in","r",stdin);
// freopen("15.out","w",stdout);
cin>>R>>a>>b>>n;
for(int i=1;i<=n;i++)cin>>x[i]>>y[i]>>r[i];
for(int i=1;i<=n;i++)p[i]=i;
p[0]=0;p[n+1]=n+1;
for(int i=1;i<=n;i++){
if(abs(y[i]-a)<=R)merge(0,i);
if(abs(y[i]-b)<=R)merge(n+1,i);
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
int D=r[i]+r[j];
if(cal(i,j)<=D*D)merge(i,j);
}
}
if(find(0)==find(n+1))cout<<"NO";
else cout<<"YES";
}
大家可以考虑一下如果让你求最大白球半径怎么做
B
签到题
只需要倒着做就好了
#include<iostream>
#include<vector>
#include<stdio.h>
using namespace std;
const int N=1e7+10;
int n,m,q,p[N],u[N],v[N],vis[N],op[N],val[N];
int del[N];
vector<int>ans;
int find(int x){
if(p[x]==x)return x;
return p[x]=find(p[x]);
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// freopen("3.in","r",stdin);
// freopen("3.out","w",stdout);
cin>>n>>m>>q;
for(int i=1;i<=n;i++)p[i]=i;
for(int i=1;i<=m;i++)cin>>u[i]>>v[i],vis[i]=1;
for(int i=1;i<=q;i++){
cin>>op[i];
if(op[i]==1){
int x;
cin>>x;
val[i]=x;//第i次操作是删第x条边
if(vis[x]==1){
vis[x]=0;//第x条边被删了
del[x]=i;//删掉第x条边是在第i次操作
}
}
}
for(int i=1;i<=m;i++){
if(vis[i]){
int a=u[i];
int b=v[i];
if(find(a)==find(b))continue;
else p[find(a)]=find(b);
}
}
int cnt=0;
for(int i=1;i<=n;i++)if(find(i)==i)cnt++;
for(int i=q;i>=1;i--){
if(op[i]==1){
int x=val[i];
if(del[x]!=i)continue;
int a=u[x];
int b=v[x];
if(find(a)==find(b))continue;
else p[find(a)]=find(b),cnt--;
}
else ans.push_back(cnt);
}
for(int i=ans.size()-1;i>=0;i--)cout<<ans[i]<<'\n';
}
C
考虑二分答案,求区间内交点个数
如果直线与圆有交点的话,就把两个交点的极角保留下来
对所有的极角离散化,最后交点相当于求有多少个 有序对 ( i , j ) 满足 Li<=Lj<=Ri<=Rj
可以直接线段树或者树状数组做
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5;
const double eps = 1e-5;
int n, k, m, m2, ans, a[N], b[N], c[N], f[N << 1];
double v[N << 1];
struct Node { double l, r; }p[N];
struct Node2 { int l, r; }q[N];
inline void upd(int i, int x) {
while (i <= m2) f[i] += x, i += (i & -i);
}
inline void qry(int i) {
while (i) ans += f[i], i &= i - 1;
}
int calc(double r) {
m = 0, m2 = 0, ans = 0;
for (int i = 1; i <= n; ++i) { //处理直线
int A = a[i], B = b[i], C = c[i];
double d = (A * A + B * B) * r * r - C * C;
if (d < 0) continue;
d = sqrt(d);
double tl = atan2(-B * C + A * d, -A * C - B * d);
double tr = atan2(-B * C - A * d, -A * C + B * d);
if (tl > tr) swap(tl, tr);
v[++m2] = tl, v[++m2] = tr;
p[++m] = { tl,tr };
}
sort(v + 1, v + m2 + 1);
for (int i = 1; i <= m; ++i) { //离散化
q[i].l = lower_bound(v + 1, v + m2 + 1, p[i].l) - v;
q[i].r = lower_bound(v + 1, v + m2 + 1, p[i].r) - v;
}
sort(q + 1, q + m + 1, [](Node2 x, Node2 y) { return x.r < y.r; });
memset(f, 0, sizeof f);
for (int i = 1; i <= m; ++i) { //计算答案
qry(q[i].l);
upd(q[i].l + 1, 1);
upd(q[i].r, -1);
}
return ans;
}
int main() {
int t=1;
//cin >> t;
for (int i = 0; i < t; i++) {
cin >> n >> k;
for (int i = 1; i <= n; ++i)
cin >> a[i] >> b[i] >> c[i];
double l = 0, r = 3e6, mid;
while (r - l > eps) { //二分
mid = (l + r) / 2;
calc(mid) < k ? l = mid : r = mid;
}
printf("%.6lf\n", r);
}
return 0;
}
#include<bits/stdc++.h>
#define y1 trcyvubinm
#define ls u<<1
#define rs u<<1|1
using namespace std;
#define int long long
const int N=5e4+10;
// const int N=1e5+10;
int n,k;
double A[N],B[N],C[N];
double L[N],R[N];
int o[N],ok[N];
struct node{
int a[N*8];
void init(){
memset(a,0,sizeof a);
}
void pushup(int u){
a[u]=a[ls]+a[rs];
}
void modify(int u,int x,int l=0,int r=n*2){
if(l==r){
a[u]++;
return;
}
int mid=l+r>>1;
if(x<=mid)modify(ls,x,l,mid);
else modify(rs,x,mid+1,r);
pushup(u);
}
int query(int u,int x,int l=0,int r=n*2){
if(x>=r)return a[u];
if(x<l)return 0;
if(l==r)return 0;
int mid=l+r>>1;
return query(ls,x,l,mid)+query(rs,x,mid+1,r);
}
}T[2];
typedef pair<double,double>PDD;
int tr[N*2];
void add(int x,int c=1){
for(int i=x;i<2*N;i+=i&-i)tr[i]+=c;
}
int query(int x){
int ans=0;
for(int i=x;i;i-=i&-i)ans+=tr[i];
return ans;
}
int check(double d){
vector<double>v;
T[0].init(),T[1].init();
vector<PDD>S;
for(int i=1;i<=n;i++){
double D=C[i]*C[i];
if(D>(A[i]*A[i]+B[i]*B[i])*d*d)continue;
if(A[i]==0){
double y1=-C[i]/B[i];
double x1=-sqrt(d*d-y1*y1);
double l=atan2(y1,x1);
double y2=y1;
double x2=sqrt(d*d-y1*y1);
double r=atan2(y2,x2);
if(l>r)swap(l,r);
v.push_back(l);
v.push_back(r);
S.push_back({r,l});
continue;
}
if(B[i]==0){
double x1=-C[i]/A[i];
double y1=-sqrt(d*d-x1*x1);
double l=atan2(y1,x1);
double x2=x1;
double y2= sqrt(d*d-x2*x2);
double r=atan2(y2,x2);
if(l>r)swap(l,r);
v.push_back(l);
v.push_back(r);
S.push_back({r,l});
continue;
}
double a=A[i]*A[i]+B[i]*B[i];
double b=2*A[i]*C[i];
double c=C[i]*C[i]-B[i]*B[i]*d*d;
if(b*b-4*a*c<0)continue;
double x1=-b/(2*a)-sqrt(b*b-4*a*c)/(2*a);
double y1=(-C[i]-A[i]*x1)/B[i];
double l=atan2(y1,x1);
double x2=-b/(2*a)+sqrt(b*b-4*a*c)/(2*a);
double y2=(-C[i]-A[i]*x2)/B[i];
double r=atan2(y2,x2);
if(l>r)swap(l,r);
v.push_back(l);
v.push_back(r);
S.push_back({r,l});
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
sort(S.begin(),S.end());
int sum=0;
memset(tr,0,sizeof tr);
for(auto t:S){
double R=t.first;
int r=lower_bound(v.begin(),v.end(),R)-v.begin()+1;
double L=t.second;
int l=lower_bound(v.begin(),v.end(),L)-v.begin()+1;
sum+=query(l);
add(l,1);
add(r+1,-1);
}
return sum;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>A[i]>>B[i]>>C[i];
double l=0,r=3e6;
while(r-l>1e-6){
double mid=(l+r)/2;
if(check(mid)>=k)r=mid;
else l=mid;
}
printf("%.6lf",r);
}
D
考虑遍历数组A,对Ai进行讨论。为了避免重复计算还要记录Ai上一次出现的位置d (边遍历边更新)。
计算区间长度R-L+1=Ai,L>d,且区间包含Ai的区间【L,R】的个数。
#include<bits/stdc++.h>
using namespace std;
int main() {
int tc; cin >> tc;
while (tc--) {
int n; cin >> n;
vector< int >a(n);
for (int i = 0; i < n; i++)cin >> a[i];
long long res = 0;
map< int, int > mp;
for (int i = 0; i < n; i++) {
int l = a[i];
int s = (i - l + 1 >= 0) ? i - l + 1 : 0;
map< int, int >::iterator itr = mp.find(a[i]);
if (itr != mp.end() and (*itr).second >= s) s = (*itr).second + 1;
int k = (s + l - 1 >= n) ? n - 1 : s + l - 1;
if (k - s + 1 != l)continue;
res = res + min(i - s + 1, n - k);
mp[a[i]] = i;
}
cout << res << "\n";
}
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, T, last[N];
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> T;
while(T--)
{
cin >> n;
memset(last, 0, sizeof last);
int ans = 0;
for(int i = 1; i <= n; ++i)
{
int x; cin >> x;
int l = max(i - x + 1, last[x] + 1), r = min(i, n - x + 1);
last[x] = i;
if(l <= r) ans += r - l + 1;
}
cout << ans << '\n';
}
return 0;
}
E
考虑dp, f [ i ] [ j ] = { a , b } f[i][j]=\{a,b\} f[i][j]={a,b}表示走到 i i i 号景点且当前出行方式为$ j$ 时所用的最短时间为 a ,最少更换次数为 b 。其中 j = 0 j=0 j=0表示骑共享单车, j = 1 j=1 j=1表示打车。
假设 i − 1 i-1 i−1号点与 i i i 号点的横坐标差值为 d x = a b s ( x i − x i − 1 ) dx=abs(x_i-x_{i-1}) dx=abs(xi−xi−1),纵坐标差值为 d y = a b s ( y i − y i − 1 ) dy=abs(y_i-y_{i-1}) dy=abs(yi−yi−1),若不考虑换车时间,两者最短路径为 d = m i n ( d x , d y ) + a b s ( d x − d y ) d=min(dx,dy)+abs(dx-dy) d=min(dx,dy)+abs(dx−dy)。( m i n ( d x , d y ) min(dx,dy) min(dx,dy)为斜着走的路程, a b s ( d x − d y ) abs(dx-dy) abs(dx−dy)为上下左右走的路程)
状态转移如下:
-
若从 i − 1 i-1 i−1 到 i i i 的过程中不换出行方式
a. 若骑共享单车则只能往上下左右走,花费时间为 d x + d y dx+dy dx+dy,即 f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] + { d x + d y , 0 } f[i][0]=f[i-1][0]+\{dx+dy,0\} f[i][0]=f[i−1][0]+{dx+dy,0}
b. 若打车则只能斜走,且仅当 ( d x + d y ) % 2 = = 0 (dx+dy)\%2==0 (dx+dy)%2==0时能够到达,花费时间为 d d d,即 f [ i ] [ 1 ] = f [ i − 1 ] [ 1 ] + { d , 0 } f[i][1]=f[i-1][1]+\{d,0\} f[i][1]=f[i−1][1]+{d,0}
-
若从 i − 1 i-1 i−1 到 i i i 的过程中换出行方式,由于两点之间最少只需要换一次出行方式即可到达,所以到 i i i号点的最短时间为到 i − 1 i-1 i−1号点的最短时间加上最短路程+1,即
a. f [ i ] [ 0 ] = m i n ( f [ i ] [ 0 ] , f [ i − 1 ] [ 1 ] + { d + 1 , 1 } ) f[i][0]=min(f[i][0],f[i-1][1]+\{d+1,1\}) f[i][0]=min(f[i][0],f[i−1][1]+{d+1,1})
b. f [ i ] [ 1 ] = m i n ( f [ i − 1 ] [ 1 ] , f [ i − 1 ] [ 0 ] + { d + 1 , 1 } ) f[i][1]=min(f[i-1][1],f[i-1][0]+\{d+1,1\}) f[i][1]=min(f[i−1][1],f[i−1][0]+{d+1,1})
最后取 f [ n × m ] [ 0 ] f[n \times m][0] f[n×m][0]和 f [ n × m ] [ 1 ] f[n \times m][1] f[n×m][1]的较小值即可。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
#define x first
#define y second
pii operator + (const pii& a, const pii& b){return {a.x + b.x, a.y + b.y};}
const int N = 1010, inf = 0x3f3f3f3f;
int n, m;
pii f[N * N][2], p[N * N];
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
{
int x; cin >> x;
p[x] = {i, j};
}
for(int i = 2; i <= n * m; ++i) f[i][0] = f[i][1] = {inf, inf};
f[1][0] = f[1][1] = {0, 0};
for(int i = 2; i <= n * m; ++i)
{
int dx = abs(p[i].x - p[i - 1].x), dy = abs(p[i].y - p[i - 1].y);
int d = min(dx, dy) + abs(dx - dy);
f[i][0] = f[i - 1][0] + make_pair(dx + dy, 0);
if((dx + dy) % 2 == 0) f[i][1] = f[i - 1][1] + make_pair(d, 0);
f[i][0] = min(f[i][0], f[i - 1][1] + make_pair(d + 1, 1));
f[i][1] = min(f[i][1], f[i - 1][0] + make_pair(d + 1, 1));
}
pii ans = min(f[n * m][0], f[n * m][1]);
cout << ans.x << ' ' << ans.y;
return 0;
}
F
思维题。首先题意可以理解为给一个数组和一个整数 k k k,要求从 [ 0 , k ] [0,k] [0,k]内选取最少数量的数字加入数组中,使得 [ 0 , k ] [0,k] [0,k]区间内的任何一个数都能通过数组中某几个数字的和来表示。
假设当前数组中的数字可以拼出从 0 0 0到 s s s的所有数字,那么在数组中加入数字 m m m,并且 m ≤ s m \leq s m≤s,就可以拼出从 0 0 0到 s + m s+m s+m的所有数字,因为区间运算 [ 0 , s ] ∪ [ 0 + m , s + m ] = [ 0 , s + m ] [0,s] \cup [0+m,s+m]=[0,s+m] [0,s]∪[0+m,s+m]=[0,s+m],这里加了 m ≤ s m\leq s m≤s的限制的原因是,保证新区间的数字也是不间断的。
因此,我们可以从小到大遍历数组中的各个数字,然后维护一个连续区间 [ 0 , s ] [0,s] [0,s],因为每个数字的加入,都会构成一个新的拼接区间,这里需要注意的是,如果当新加入的数字不能保证区间连续时,或者数组中所有数字遍历完,仍然不能保证区间 [ 1 , s ] [1,s] [1,s]能覆盖 [ 1 , k ] [1,k] [1,k]时怎么办,就应该往数组中加入 s s s,获得 [ 1 , s + s ] [1,s+s] [1,s+s]的连续区间, 此时++ans。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, k, a[N];
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> k;
for(int i = 0; i < n; ++i) cin >> a[i];
sort(a, a + n);
long long x = 1; //这里的x是题解中的s+1,即第一个不在区间里的数
int i = 0, ans = 0;
while(x <= k)
{
if(i < n && a[i] <= x) x += a[i], ++i;
else x <<= 1, ans++;
}
cout << ans;
return 0;
}