2023csoj寒假训练10

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

可以直接线段树或者树状数组做

img

#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 i1号点与 i i i 号点的横坐标差值为 d x = a b s ( x i − x i − 1 ) dx=abs(x_i-x_{i-1}) dx=abs(xixi1),纵坐标差值为 d y = a b s ( y i − y i − 1 ) dy=abs(y_i-y_{i-1}) dy=abs(yiyi1),若不考虑换车时间,两者最短路径为 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(dxdy)。( m i n ( d x , d y ) min(dx,dy) min(dx,dy)为斜着走的路程, a b s ( d x − d y ) abs(dx-dy) abs(dxdy)为上下左右走的路程)

状态转移如下:

  1. 若从 i − 1 i-1 i1 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[i1][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[i1][1]+{d,0}

  2. 若从 i − 1 i-1 i1 i i i 的过程中换出行方式,由于两点之间最少只需要换一次出行方式即可到达,所以到 i i i号点的最短时间为到 i − 1 i-1 i1号点的最短时间加上最短路程+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[i1][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[i1][1],f[i1][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 ms,就可以拼出从 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 ms的限制的原因是,保证新区间的数字也是不间断的。

因此,我们可以从小到大遍历数组中的各个数字,然后维护一个连续区间 [ 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值