07.12-个人赛

07.12-个人赛

A

题意

有一个非负整数 n ( n ≤ 1 0 9 ) n(n \le 10^{9}) n(n109)

给出它在二进制和三进制下的表示

二进制和三进制的表示中都恰好有一位有误

求出原来的 n n n (用十进制表示)

思路

枚举二进制下哪一位有误,然后修改那一位并假设它就是答案,再和题目给出的三进制进行比较

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    string st2,st3;
    cin>>st2>>st3;
    reverse(st2.begin(),st2.end());
    reverse(st3.begin(),st3.end());
    for (int i=0;i<st2.size();++i)
    {
        int res = 0;
        for (int j=0;j<st2.size();++j)
        {
            if (i==j) res+=((st2[j]-'0')^1)<<j;
            else res+=(st2[j]-'0')<<j;
        }
        vector <int> vc;
        int n = res;
        for (;res;)
        {
            vc.push_back(res%3);
            res/=3;
        }
        if (vc.size()!=st3.size()) continue;
        int diff = 0;
        for (int j=0;j<vc.size();++j) diff+=(st3[j]!=(vc[j]+'0'));
        if (diff==1)
        {
            cout<<n<<endl;
            break;
        }
    }
    return 0;
}

B

题意

给出 n n n 个数,问有多少个区间满足它的中位数 ≥ X \ge X X

思路

中位数 ≥ X \ge X X 这个条件可以近似地看成,至少一半的数都 ≥ X \ge X X

≥ X \ge X X 的数视为 1 1 1 ,将 < X < X <X 的数视为 − 1 -1 1

于是问题转化为有多少个区间满足它的和 ≥ 0 \ge 0 0 (本题的中位数比较特殊)

这种静态的对于区间和的统计一般都可以转化为前缀和问题

比如对于区间 [ l , r ] [l,r] [l,r] 来说,它的和就是 s [ r ] − s [ l − 1 ] s[r]-s[l-1] s[r]s[l1]

统计有多少个和 ≥ 0 \ge 0 0 的区间就是统计有多少对 [ l , r ] [l,r] [l,r] 满足 s [ r ] − s [ l − 1 ] ≥ 0 s[r]-s[l-1] \ge 0 s[r]s[l1]0 也就是 s [ l − 1 ] ≤ s [ r ] s[l-1] \le s[r] s[l1]s[r]

等价于统计有多少对 ( i , j ) (i,j) (i,j) 满足 i < j i < j i<j 并且 s [ i ] < s [ j ] s[i] < s[j] s[i]<s[j] ,也就相当于求正序对了

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
inline int lowbit(const int x)
{
    return x&-x;
}
int tr[N+N];
int a[N],s[N];
int n,X;
void add(int x)
{
    for (int i=x;i<N+N;i+=lowbit(i)) ++tr[i];
}
int calc(int x)
{
    int res=0;
    for (int i=x;i;i^=lowbit(i)) res+=tr[i];
    return res;
}
int main()
{
    cin>>n>>X;
    for (int i=1,x;i<=n;++i) scanf("%d",&x),a[i]=((x>=X) ? 1 : -1);
    for (int i=1;i<=n;++i) s[i]=s[i-1]+a[i];
    long long ans = 0;
    add(0+N);
    for (int i=1;i<=n;++i)
    {
        ans+=calc(s[i]+N);
        add(s[i]+N);
    }
    cout<<ans<<'\n';
    return 0;
}

C

题意

给出两个序列 A A A B B B

A A A 中有多少个区间长得像 B B B

长的像是指满足以下条件

​ 1.区间长度与 B B B 的长度相同

​ 2.经过若干次重新排列以及整体加减后和 B B B 完全一致

思路

枚举区间,然后排个序,比较差值是否一致

#include <bits/stdc++.h>
using namespace std;
const int N = 1500005;
int a[N],b[N];
int n,m;
int ck(int pos)
{
    int c[105];
    for (int i=1;i<=m;++i) c[i]=a[pos+i-1];
    sort(c+1,c+1+m);
    for (int i=2;i<=m;++i) if (c[i] - c[1] != b[i] - b[1]) return 0;
    return 1;
}
int main()
{
    cin>>n;for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    cin>>m;for (int i=1;i<=m;++i) scanf("%d",&b[i]);
    sort(b+1,b+1+m);
    vector <int> ans;
    for (int i=1;i+m-1<=n;++i)
    {
        if (ck(i)) ans.push_back(i);
    }
    cout<<ans.size()<<'\n';
    for (auto it:ans) cout<<it<<'\n';
    return 0;
}

D

题意

给出一个颜色序列

求出最小的区间使得它包含所有颜色

思路

暴力枚举就是先枚举左端点,然后右端点往右扫过去,直到所有颜色都包含

显然,对于更靠右的左端点,它对应的右端点要么不动,要么更靠右

所以就不用每次右端点都重新扫了,可以从上一次的右端点开始接着扫

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct pll { ll  x,y; };
const int N = 1500005;
map <ll,int> mp;
pll a[N];
int n,totcnt;
int main()
{
    cin>>n;
    for (int i=1;i<=n;++i)
    {
        scanf("%lld%lld",&a[i].x,&a[i].y);
        if (mp[a[i].y]==0) ++totcnt;
        ++mp[a[i].y];
    }
    mp.clear();
    sort(a+1,a+1+n,[&](pll x,pll y)
        {
             return x.x<y.x;
        }
    );
    long long ans = 1LL<<60;
    int cnt = 0;
    for (int l=1,r=0;l<=n && r<=n;)
    {
        while (cnt < totcnt && r<=n)
        {
            ++r;
            if (r>n) break;
            if (mp[a[r].y]==0) ++cnt;
            ++mp[a[r].y];
        }
        if (r>n) break;
        ans=min(ans,a[r].x-a[l].x);
        --mp[a[l].y];
        if (mp[a[l].y]==0) --cnt;
        ++l;
    }
    cout<<ans<<'\n';
    return 0;
}

E

给出一张黑白图,上面有两个不相邻的黑色的联通块

问最少把几个白色格子染黑,来把这两个联通块连起来

求出两部分之间的最短路即可

#include <bits/stdc++.h>
using namespace std;
struct pii { int x,y; };
#define pb push_back
const int N = 1505;
const pii dir[4] = {{-1,0},{1,0},{0,-1},{0,1}};
pii operator + (pii x,pii y) {return {x.x+y.x,x.y+y.y};};
int a[N][N],dis[N][N];
int n,m;
void dfs(pii now)
{
    if (a[now.x][now.y]==0) return;
    if (dis[now.x][now.y] < 1e9) return;
    dis[now.x][now.y]=0;a[now.x][now.y]=2;
    for (int i=0;i<4;++i) dfs(now+dir[i]);
}
int main()
{
    string st;
    cin>>n>>m;
    int stx,sty;
    for (int i=1;i<=n;++i)
    {
        cin>>st;
        for (int j=1;j<=m;++j)
        {
            a[i][j]=((st[j-1]=='X') ? 1 : 0);
            if (a[i][j]) stx=i,sty=j;
            dis[i][j] = 1e9;
        }
    }
    dfs({stx,sty});
    //for (int i=1;i<=n;++i) {for (int j=1;j<=m;++j) cout<<a[i][j]<<' ';cout<<endl;};
    queue <pii> q;
    for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) if (a[i][j]==2) q.push({i,j});
    while (!q.empty())
    {
        pii now = q.front();q.pop();
        for (int i=0;i<4;++i)
        {
            pii nw = now + dir[i];
            if (nw.x<1 || nw.y<1 || nw.x>n || nw.y>m) continue;
            if (dis[nw.x][nw.y]<1e9) continue;
            dis[nw.x][nw.y]=dis[now.x][now.y]+1;
            q.push(nw);
            if (a[nw.x][nw.y]==1)
            {
                cout<<dis[nw.x][nw.y]-1<<'\n';
                return 0;
            }
        }
    }
    return 0;
}

F

给出一个时间

求从11日11点11分到这个时间过了多久

若这个时间比11日11点11分要早,输出 − 1 -1 1

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    int D,H,M;
    cin>>D>>H>>M;
    int st = 11*24*60+11*60+11;
    int ed = D *24*60+H *60+M ;
    cout<<((st<=ed) ? (ed-st) : -1);
    return 0;
}

G

题意

给出一个网格图,多次询问一个子矩形内联通块个数

思路

不会

H

题意

给出一张黑白图,上面有三个不相邻的黑色的联通块

问最少把几个白色格子染黑,来把这三个联通块连起来

思路

和E不一样的是,这题有三个联通块,一个显然的想法是枚举连接方式,然后转化为E题的做法

但是这不一定是最优的,比如下面这种情况

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XMf7etTm-1626081128584)(C:\Users\ytx22\AppData\Roaming\Typora\typora-user-images\image-20210712133004669.png)]

上面两种连接方式显然不如下面这种

于是就懒得多想了,直接上最小斯坦纳树板子

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct pii { int x,y; };
const pii dir[4] = {{-1,0},{1,0},{0,-1},{0,1}};
pii operator + (pii x,pii y) {return {x.x+y.x,x.y+y.y};};
struct pll {ll x,y;};
typedef vector <int> vi;
typedef vector <pll> vll;
#define pb push_back
const int N = 2605;
ll dp[10][N];
int inq[N],qcnt[N];
int spfa(vll * adj,ll * dp,const int n)
{
    queue <int> q;
    for (int i=1;i<=n;++i) inq[i]=1,q.push(i),qcnt[i]=0;
    while (!q.empty())
    {
        int now=q.front();q.pop();inq[now]=0;++qcnt[now];
        if (qcnt[now]>n) return -1;
        for (auto nw:adj[now])
        if (dp[now]+nw.y<dp[nw.x])
        {
            dp[nw.x]=dp[now]+nw.y;
            if (!inq[nw.x])
            {
                inq[nw.x]=1;
                q.push(nw.x);
            }
        }
    }
    return 0;
}
ll Min_Steiner_Tree(vll * adj,const vi & key,const int n)
{
    int ALL = (1 << key.size()) - 1;
    memset(dp,0x3f,sizeof(dp));
    for (int i=1;i<=n;++i) dp[0][i]=0;
    for (int i=0;i<key.size();++i) dp[1<<i][key[i]]=0;
    for (int mask=1;mask<=ALL;++mask)
    {
        for (int subset=mask;subset>0;subset=(subset-1)&mask)
        {
            for (int i=1;i<=n;++i)
            {
                dp[mask][i]=min(dp[mask][i],dp[subset][i]+dp[mask^subset][i]);
            }
        }
        int err=spfa(adj,dp[mask],n);
    }
    int mnid=1;
    for (int i=1;i<=n;++i) if (dp[ALL][i]<dp[ALL][mnid]) mnid=i;
    return dp[ALL][mnid];
}
vll adj[N];
int a[N][N],vis[N][N];
int n,m,k;
int row,column;
void dfs(pii now,int col)
{
    if (a[now.x][now.y]>=0) return;
    a[now.x][now.y]=col;
    for (int i=0;i<4;++i) dfs(now+dir[i],col);
}
inline int getid(int i,int j)
{
    return (i-1)*column+j;
}
int main()
{
    string st;
    cin>>row>>column;
    for (int i=1;i<=row;++i)
    {
        cin>>st;
        for (int j=1;j<=column;++j)
        {
            a[i][j]=((st[j-1]=='X') ? -1 : 0);
        }
    }
    int cnt = 0;
    for (int i=1;i<=row;++i)
    {
        for (int j=1;j<=column;++j)
        if (a[i][j]==-1)
        {
            dfs({i,j},++cnt);
        }
    }
    //for (int i=1;i<=row;++i) {for (int j=1;j<=column;++j) cout<<a[i][j]<<' ';cout<<endl;};
    vector <int> key;
    for (int id=1;id<=cnt;++id) key.pb(row*column+id);
    for (int i=1;i<=row;++i)
    for (int j=1;j<=column;++j)
    {
        if (a[i][j]>0) adj[getid(i,j)].pb({row*column+a[i][j],0}),adj[row*column+a[i][j]].pb({getid(i,j),0});
        if (i>1) adj[getid(i,j)].pb({getid(i-1,j),1});
        if (j>1) adj[getid(i,j)].pb({getid(i,j-1),1});
        if (i<row) adj[getid(i,j)].pb({getid(i+1,j),1});
        if (j<column) adj[getid(i,j)].pb({getid(i,j+1),1});
    }
    //for (auto it:key) cout<<it<<' ';cout<<'\n';
    n = row * column + cnt;
    ll ans=Min_Steiner_Tree(adj,key,n);
    cout<<ans-cnt+1<<endl;
    return 0;
}

课后练习:最小斯坦纳树

I

题意

给出 n n n 条线段

最多能选出几条线段使它们没有交点

思路

把竖直的线段放左边,水平的线段放右边

把相交的线段之间连起来,发现这是一张二分图

我们要求的就是它的最大独立集

在二分图上,满足 最大独立集=点数-最大匹配数

直接跑一个二分图匹配

如下图所示,判断线段是否相交也是比较简单的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UDOelMag-1626081128587)(C:\Users\ytx22\AppData\Roaming\Typora\typora-user-images\image-20210712154532983.png)]

PS:这个板子年代有点久远了,不建议直接抄

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
const int N = 1505;
int n,m;
struct Directed_Graph
{
    int v_n,e_n;
    int head[1005];
    struct Edge
    {
        int st,ed,nxt;
    }e[250005];
    void init()
    {
        v_n=e_n=0;
        memset(head,0,sizeof(head));
    }
    void addedge(int u,int v)
    {
        ++e_n;
        e[e_n].st=u;
        e[e_n].ed=v;
        e[e_n].nxt=head[u];
        head[u]=e_n;
    }
    int flag[1005],match[1005];
    int augment(int x)
    {
        if (x==0) return 1;
        for (int i=head[x];i;i=e[i].nxt)
        if (!flag[e[i].ed])
        {
            int y=e[i].ed;
            flag[y]=1;
            if (!augment(match[y])) continue;
            match[y]=x;
            return 1;
        }
        return 0;
    }
    int getmatch()
    {
        memset(match,0,sizeof(match));
        int match_tot=0;
        for (int i=1;i<=n;++i)
        {
            memset(flag,0,sizeof(flag));
            match_tot+=augment(i);
        }
        return match_tot;
    }
}g1;
struct Node
{
    int u1,v1,u2,v2;
}a[N],b[N];
int ck(const Node & x,const Node & y)
{
    if (y.u1<=x.u1 && x.u1<=y.u2)
    {
        if (x.v1<=y.v1 && y.v1<=x.v2)
            return 1;
    }
    return 0;
}
int main()
{
    vector <Node> vca,vcb;
    int n1;cin>>n1;
    for (int i=1;i<=n1;++i)
    {
        int u1,v1,u2,v2;
        cin>>u1>>v1>>u2>>v2;
        if (u1==u2)
        {
            if (v1>v2) swap(v1,v2);
            vca.pb(a[++n]={u1,v1,u2,v2});
        }else
        {
            if (u1>u2) swap(u1,u2);
            vcb.pb(b[++m]={u1,v1,u2,v2});
        }
    }
    int ans = max(vca.size(),vcb.size());
    for (int i=1;i<=n;++i)
    {
        for (int j=1;j<=m;++j)
        {
            if (ck(a[i],b[j]))
            {
                g1.addedge(i,n+j);
            }
        }
    }
    g1.v_n=n+m;
    int res = g1.getmatch();
    ans=max(ans,n+m-res);
    cout<<ans<<endl;
    return 0;
}

J

K

题意

给出 n n n 个数,对它们进行修改使它们的平方和恰好为 m m m

每个数只能修改一次,修改的代价为 ( 修 改 后 的 数 − 修 改 前 的 数 ) 2 (修改后的数-修改前的数)^{2} ()2

求最小花费

思路

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个数,平方和为 j j j 的最小花费

枚举下一个数修改后的值进行转移

时间复杂度 O ( N ∗ M ∗ M A X ( A i ) ) O(N*M*MAX(A_{i})) O(NMMAX(Ai))

#include <bits/stdc++.h>
using namespace std;
const int N = 1500005;
int a[15];
int dp[11][10005];
int n,m;
int main()
{
    cin>>n>>m;
    for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    memset(dp,60,sizeof(dp));
    //cout<<"inf="<<dp[0][0]<<endl;
    dp[0][0]=0;
    for (int i=0;i<n;++i)
    for (int j=0;j<m;++j)
    if (dp[i][j]<1e9)
    {
        for (int nw=0;nw*nw<=m-j;++nw)
        {
            dp[i+1][j+nw*nw]=min(dp[i+1][j+nw*nw],dp[i][j]+(a[i+1]-nw)*(a[i+1]-nw));
        }
    }
    cout<<((dp[n][m]>=1e9) ? -1 : dp[n][m])<<'\n';
    return 0;
}

L

题意

给出一个 9 ∗ 9 9*9 99 0 / 1 0/1 0/1 网格

对它进行修改,使它每行,每列,每个 3 ∗ 3 3*3 33 都恰好有偶数个 1 1 1

求最少修改次数

思路

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示前 i i i 行, 每一列的奇偶性的状压值是 j j j ,当前的三个 3 ∗ 3 3*3 33 的奇偶性的状压值是 k k k 的最小修改次数

枚举下一行的情况进行转移

时间复杂度 O ( 9 ∗ 2 9 ∗ 2 3 ∗ 2 9 ) = O ( 9 ∗ 2 21 ) O(9*2^{9}*2^{3}*2^{9})=O(9*2^{21}) O(9292329)=O(9221)

#include <bits/stdc++.h>
using namespace std;
int dp[10][513][9];
int a[15],c[1005][4];
int n = 9;
int main()
{
    string st;
    for (int i=1;i<=n;++i)
    {
        cin>>st;
        for (int j=0;j<n;++j) a[i]=a[i]|((st[j]-'0')<<j);
    }
    for (int i=0;i<(1<<n);++i)
    {
        int cnt = 0;
        for (int j=0;j<n;++j) cnt+=((i>>j)&1);
        for (int j=0;j<3;++j) c[i][1]+=((i>>j)&1);
        for (int j=3;j<6;++j) c[i][2]+=((i>>j)&1);
        for (int j=6;j<9;++j) c[i][3]+=((i>>j)&1);
        c[i][0]=c[i][1]+c[i][2]+c[i][3];
    }
    memset(dp,60,sizeof(dp));
    dp[0][0][0]=0;
    for (int i=0;i<n;++i)
    {
        int ni = i + 1,nj,nk;
        for (int j=0;j<(1<<n);++j)
        {
            for (int k=0;k<(1<<3);++k)
            {
                //cout<<"now="<<i<<' '<<j<<' '<<k<<endl;
                for (int nw = 0;nw < (1<<n);++nw)
                if (!(c[nw][0]&1))
                {
                    //cout<<"nw="<<nw<<endl;
                    nj=j^nw;
                    if ((i+1)%3>0)
                    {
                        if (i%3==0) nk = (c[nw][1]&1) | ((c[nw][2]&1)<<1) | ((c[nw][3]&1)<<2);
                        else nk = k ^ ((c[nw][1]&1) | ((c[nw][2]&1)<<1) | ((c[nw][3]&1)<<2));
                        if (i==1 && j==0 && k==0 && nw==0)
                        {
                            //cout<<"trans="<<ni<<' '<<nj<<' '<<nk<<endl;
                        }
                        dp[ni][nj][nk]=min(dp[i][j][k]+c[nw^a[ni]][0],dp[ni][nj][nk]);
                    }else
                    {
                        if (((k>>0)&1)^(c[nw][1]&1)) continue;
                        if (((k>>1)&1)^(c[nw][2]&1)) continue;
                        if (((k>>2)&1)^(c[nw][3]&1)) continue;
                        nk=0;
                        dp[ni][nj][nk]=min(dp[i][j][k]+c[nw^a[ni]][0],dp[ni][nj][nk]);
                    }
                }
            }
        }
    }
    cout<<dp[n][0][0]<<endl;
    return 0;
}

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值