ZJNU 2021-07-15 个人排位赛4 部分题解

[广告位招租]

Update:I题已补


A - Acowdemia I

Link

题意

Bessie写了\(N\)篇论文,第\(i\)篇论文被引用了\(c_i\)

\(h\)-index表示一个最大的值\(h\),使得至少有\(h\)篇论文被引用的次数多于\(h\)

现在Bessie想写一篇新论文,用来刷旧论文的引用次数

每篇旧论文最多被引用一次,并且这篇新论文中最多引用\(L\)篇旧论文

问在写完这篇新论文后,\(h\)-index的最大值是多少

Bessie的做法可能会引发学术诚信问题,建议大家不要学习

标签

Implementation

Prefix-Sum

思路

因为只能写一篇新论文,并且每篇旧论文最多被引用一次

引用次数的数据范围只有\(10^5\),所以可以先记录下每种引用次数的论文有多少篇(\(cnt[i]\)表示引用次数为\(i\)的论文篇数)

直接枚举\(h\)作为引用次数的下限,那么可以根据前缀和直接取得原先引用次数\(\ge h\)的篇数\(x\)

再贪心让新论文全部引用在引用次数为\(h-1\)的旧论文上,使其尽可能增加一次引用次数,涉及到的论文篇数为\(y=min(L,cnt[h-1])\)

最后判断\(x+y\ge h\)是否成立即可

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

int n,l;
int a[100050],b[100050],s[100050];

void solve()
{
    cin>>n>>l;
    rep(i,1,n)
    {
        cin>>a[i];
        b[a[i]]++;
    }
    s[0]=b[0];
    rep(i,1,100000)
        s[i]=s[i-1]+b[i];
    int r=0;
    rep(i,1,n)
    {
        int t=n-s[i-1];
        //cout<<i<<' '<<t<<'\n';
        t+=min(b[i-1],l);
        if(t>=i)
            r=i;
    }
    cout<<r<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



B - Acowdemia III

Link

题意

一张\(n\times m\)的图,每个点可能有一头牛、一堆草,或者什么也没有

如果某一堆草与某两头牛都相邻,那么这两头牛就可以走一步来到这堆草所在的位置,并且两头牛可以成为朋友

一旦两头牛在某一堆草的位置成为了朋友,那么其他牛便不能再到这堆草的位置来

一头牛可以与多头其他牛成为朋友,假设每次与其他牛成为朋友后,他们都会再回到原来的位置

问最多可以让多少对牛成为朋友

标签

Implementation

思路

考虑每堆草是否能够让某两头牛成为朋友

如果与某堆草相邻的牛只有一头,明显无贡献

如果与某堆草相邻的牛大于等于三头,那么一定可以使得其中两头在这里成为朋友

如果与某堆草相邻的牛有两头,那么需要分情况讨论,特殊的有以下这两种:

\[CG\\ GC \]

\[GC\\ CG \]

如果出现上述两种情况,判断与另外对角线上的一堆草相邻的牛的数量是否也为\(2\)

如果不为\(2\),可以让另外一堆草分配给任意其他对牛(只要不同时是图中这两只牛就行),当前枚举到的这堆草贡献\(+1\)

否则会产生冲突,这两头牛可能可以同时吃这两堆草,但朋友的对数只能\(+1\)

(对于代码部分)根据枚举顺序,实际上只需要考虑两种方向就行,对于这两种冲突情况,让枚举顺序中后出现的情况不计入答案即可

(口糊极其严重建议直接看代码)

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};

int n,m;
char mp[1010][1010];
int adjcow[1010][1010];

void solve()
{
    cin>>n>>m;
    rep(i,1,n)
        cin>>mp[i]+1;
    rep(i,1,n)
        rep(j,1,m)
            repp(t,0,4)
                adjcow[i][j]+=(mp[i+dx[t]][j+dy[t]]=='C'); //相邻的牛的数量
    int ans=0;
    rep(i,1,n)
        rep(j,1,m)
            if(mp[i][j]=='G')
            {
                if(adjcow[i][j]>2)
                    ans++;//,cout<<i<<' '<<j<<'\n';
                else if(adjcow[i][j]==2)
                {
                    if(mp[i-1][j]=='C'&&mp[i][j-1]=='C'&&mp[i-1][j-1]=='G'&&adjcow[i-1][j-1]==2||
                       mp[i][j+1]=='C'&&mp[i-1][j]=='C'&&mp[i-1][j+1]=='G'&&adjcow[i-1][j+1]==2) //说明已经在另外一堆草中已经计算过这两头牛的贡献(根据枚举顺序)
                        continue;
                    ans++;//cout<<i<<' '<<j<<'\n';
                }
            }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



C - United Cows of Farmer John II

Link

防AK 😦

题意

K题的Hard版本

定义一个三元组\((i,j,k),\ 1\le i\le j\le k\le n\)是合法的,当且仅当\(i,j,k\)这三个位置所表示的数在区间\([i,k]\)中都仅出现一次

问合法的三元组数量




D - Permutation

Link

防AK 😦

题意

给定二维平面中的\(N\)个点,可以随意选择这\(N\)个点的任意一个排列\(p_1,p_2,\cdots,p_N\)

\(p_1,p_2,p_3\)三个点两两连边

\(i=4\)开始,每个点尝试与所有\(j\ (j\lt i)\)连边,要保证连的边与其他已经连的边不交叉,并且最终整张图中每个点都严格只有三条边

如果存在一个合法的连边方案能够满足上述条件,则这个排列是可行的

问存在多少种可行的排列




E - Acowdemia IV

Link

题意

A题的Hard版本

Bessie写了\(N\)篇论文,第\(i\)篇论文被引用了\(c_i\)

\(h\)-index表示一个最大的值\(h\),使得至少有\(h\)篇论文被引用的次数多于\(h\)

现在Bessie想写\(K\)篇新论文,用来刷旧论文的引用次数

每篇旧论文最多被引用一次,并且每一篇新论文中最多引用\(L\)篇旧论文

问在写完所有新论文后,\(h\)-index的最大值是多少

Bessie的做法可能会引发学术诚信问题,建议大家不要学习

标签

Implementation

Binary Search

Sorting

思路

根据A题的枚举实际可以得知,假如可以做到让\(x\)篇论文引用次数不少于\(x\)次,那么对于所有\(y\ (y\le x)\),一定也可以做到让\(y\)篇论文引用次数不少于\(y\)次(具有临界性,可二分判断最优解)

于是便可以考虑二分\(h\)-index

考虑如何检查\(h\)的可行性

先将所有旧论文的引用次数进行排序,通过贪心,可以优先考虑引用次数较多的论文算入总数

自大到小,检查是否能把新论文的所有引用全部放在次数较多的\(h\)篇旧论文上,使得这\(h\)篇旧论文最终都能够满足引用次数\(\ge h\)

根据题目,最多可以写\(K\)篇,每篇最多引用\(L\)篇旧论文

所以对于某篇旧论文,最多可以增加\(K\)次引用;而对于所有旧论文,总共最多可以增加\(K\times L\)次引用

所以先检查第\(n-h+1\)大的论文的引用次数\(+K\)后是否大于等于\(h\)(单篇最值),再检查将所有引用次数小于\(h\)的旧论文全部加到\(h\),需要的总引用量是否大于等于\(K\times L\)即可(总体最值)

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

int n,k,l;
int a[100050];

bool check(int h)
{
    if(a[n-h+1]+k<h)
        return false;
    ll sum=1LL*k*l;
    rep(i,n-h+1,n)
    {
        sum-=max(0,h-a[i]);
        if(sum<0)
            return false;
    }
    return true;
}

void solve()
{
    cin>>n>>k>>l;
    rep(i,1,n)
        cin>>a[i];
    sort(a+1,a+n+1);
    int x=0,y=n;
    while(x<=y)
    {
        int m=x+y>>1;
        if(check(m))
            x=m+1;
        else
            y=m-1;
    }
    cout<<y<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}
/*
4 3 2
1 1 1 1

4 100000 0
0 0 1 0
*/



F - Acowdemia II

Link

题意

一个实验室里有\(N\)位学生,他们发表过\(K\)篇出版物

出版物的署名顺序是按照贡献来设定的,贡献大的会写在前面,贡献小的写在后面

对于贡献相同的,署名顺序会按照名字的字典序来排序,小的在前,大的在后

已知这\(N\)位学生实际上的总贡献是互不相同的,但可能在某些出版物中贡献会是相同的


即,如果\(B\)的总贡献实际上比\(A\),那么严格意义上来说每篇出版物的署名中\(B\)都会排在\(A\)前面

但一旦\(B\)某次贡献与\(A\)相同,那么根据字典序,\(A\)可能会排在\(B\)前面

不可能出现\(B\)实际贡献比\(A\)小的情况


试根据这\(K\)篇出版物的署名,判断出这\(N\)位学生两两之间的贡献关系,如果\(i\)\(j\)贡献大,则答案矩阵\(A_{i,j}\)位置应当为\(0\)\(A_{j,i}\)位置应当为\(1\)

如果无法确定,则答案矩阵对应位置应为\(?\)

标签

Implementation

Sorting

(Optional) Hashing

思路

按照题目模拟一遍即可,不需要判断非法情况(即可以确定\(A\)某次贡献比\(B\)大,并且\(B\)在另外一次的贡献比\(A\)大的情况)

由于贡献相同按字典序排序,所以署名中如果\(s_i\rightarrow s_{i+1}\)为升序,则对两者不做处理

否则如果\(s_i\rightarrow s_{i+1}\)为降序,则可以推断\(s_i\)及此前的所有学生的贡献都比\(s_{i+1}\)及此后的所有学生的贡献要大

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

unordered_map<string,int> mp;
char ans[105][105];
string str[105];
bool cmp[105];

void solve()
{
    int n,k;
    cin>>k>>n;
    rep(i,1,n)
    {
        string s;
        cin>>s;
        mp[s]=i;
    }
    mst(ans,'?');
    rep(i,1,n)
        ans[i][i]='B';
    rep(t,1,k)
    {
        rep(i,1,n)
        {
            cin>>str[i];
            if(i>1)
                cmp[i-1]=(str[i-1]<str[i]);
        }
        rep(i,1,n) //枚举学生i
        {
            int j=i;
            while(j<n&&cmp[j])
                j++; //忽略升序
            j++; //出现降序退出循环,+1后开始处理
            while(j<=n)
            {
                int x=mp[str[i]],y=mp[str[j]]; //可以确定学生j的贡献比学生i小
                ans[x][y]='0';
                ans[y][x]='1';
                j++;
            }
        }
    }
    rep(i,1,n)
    {
        rep(j,1,n)
            cout<<ans[i][j];
        cout<<'\n';
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



G - Do You Know Your ABCs? 2

Link

题意

一开始存在三个数\(A,B,C\ (1\le A\le B\le C)\)

Elsie会从\(A,B,C,A+B,A+C,B+C,A+B+C\)这七个数中取出\(N\ (4\le N\le 7)\)个数告诉Bessie

试帮助Bessie确定这三个数有多少种类

标签

Implementation

Number Theory

思路

由于至少会给定\(4\)个数,可以发现\(A+B+C\)可能会直接给出,也可能可以通过某两个数相加得到

\(O(N^2)\)尝试构建\(A+B+C\)的值,开始模拟判断是否合法

对于给定的这\(N\)个数,将其原来的数\(d\)\(A+B+C-d\)的值一并处理出来

同样的,由于至少给定\(4\)个数,可以发现这样处理出来的\(2N\)个值中,\(A,B,C\)这三者中的至少两者一定能直接得出

\(O((2N)^2)\)枚举两者的值,再与\(A+B+C\)作差得出第三者的值

在得到可能的\(A,B,C\)的值后,检查一遍合法性即可(检查方法见代码,注意对于出现次数的处理)

如果合法,将三元组放入set中查重,最后输出set的大小作为答案

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

int n;
vector<ll> v;
set<vector<ll> > ans;

bool check(ll a,ll b,ll c) //检查这三个数能否构建出输入的vector
{
    if(c<0)
        return false;
    map<ll,int> mp;
    mp[a]++;
    mp[b]++;
    mp[c]++;
    mp[a+b]++;
    mp[a+c]++;
    mp[b+c]++;
    mp[a+b+c]++;
    for(ll &d:v)
    {
        if(--mp[d]<0)
            return false;
    }
    return true;
}

void emulate(ll sum) //以A+B+C=sum为假设进行模拟
{
    vector<ll> vec;
    for(ll &d:v)
    {
        vec.pb(d);
        vec.pb(sum-d);
    }
    for(ll &d:vec)
        if(d<0)
            return;
    int siz=vec.size();
    repp(i,0,siz)
        repp(j,i+1,siz)
            if(check(vec[i],vec[j],sum-vec[i]-vec[j]))
            {
                //cout<<vec[i]<<' '<<vec[j]<<' '<<sum-vec[i]-vec[j]<<'\n';
                vector<ll> vr;
                vr.pb(vec[i]);
                vr.pb(vec[j]);
                vr.pb(sum-vec[i]-vec[j]);
                sort(vr.begin(),vr.end());
                ans.insert(vr);
            }
}

void solve()
{
    ans.clear();
    v.clear();
    cin>>n;
    rep(i,1,n)
    {
        ll d;
        cin>>d;
        v.pb(d);
    }
    repp(i,0,n)
        repp(j,i+1,n)
            emulate(v[i]+v[j]); //sum可能可以由两者之和得到
    repp(i,0,n)
        emulate(v[i]); //sum也可能直接给出
    cout<<ans.size()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int T;cin>>T;while(T--)
        solve();
    return 0;
}



H - Least Common Ancestors

Link

标签

LCA

代码

学习LCA的在线与离线算法,详情见:

倍增 - 在线

Tarjan - 离线

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

int n,m,root;

const int MAXN=200050,MAXF=20;

vector<int> G[MAXN];
int depth[MAXN];
int father[MAXN][MAXF];
bool vis[MAXN];

void dfs(int pos,int fa)
{
    vis[pos]=true;
    depth[pos]=depth[fa]+1;
    father[pos][0]=fa;
    for(int i=1;i<MAXF;i++)
        father[pos][i]=father[father[pos][i-1]][i-1];
    int cnt=G[pos].size();
    for(int i=0;i<cnt;i++)
    {
        if(!vis[G[pos][i]])
            dfs(G[pos][i],pos);
    }
}

int lca(int a,int b)
{
    while(depth[a]<depth[b])
    {
        for(int i=MAXF-1;i>=0;i--)
        {
            if(depth[b]-(1<<i)>=depth[a])
                b=father[b][i];
        }
    }
    while(depth[a]>depth[b])
    {
        for(int i=MAXF-1;i>=0;i--)
        {
            if(depth[a]-(1<<i)>=depth[b])
                a=father[a][i];
        }
    }
    if(a==b)
        return a;
    while(father[a][0]!=father[b][0])
    {
        for(int i=MAXF-1;i>=0;i--)
        {
            if(father[a][i]!=father[b][i])
            {
                a=father[a][i];
                b=father[b][i];
            }
        }
    }
    return father[a][0];
}

void solve()
{
    cin>>n>>m>>root;
    repp(i,1,n)
    {
        int u,v;
        cin>>u>>v;
        G[u].pb(v);
        G[v].pb(u);
    }
    dfs(root,0);
    rep(i,1,m)
    {
        int u,v;
        cin>>u>>v;
        int w=lca(u,v);
        cout<<w<<' '<<depth[u]+depth[v]-2*depth[w]<<'\n';
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



I - Maze Tac Toe

Link

题意

有一个\(3\times 3\)的棋盘,初始时为空,假如某一行、某一列或者某一对角线上出现了MOO或者OOM这三个字母,则游戏结束

在一张\(N\times N\)的图上,Bessie可以随意走动

输入数据中三个字母为一个单位,'###'表示不能走的点,'BBB'表示Bessie的起始点,'...'表示可以走的点

形如'Mxy'表示如果Bessie如果走到这个点,那么Bessie就应该立即在棋盘上的\(x,y\)这个点放置一个字母'M',除非这个点已经放置了其他字母

形如'Oxy'表示如果Bessie如果走到这个点,那么Bessie就应该立即在棋盘上的\(x,y\)这个点放置一个字母'O',除非这个点已经放置了其他字母

Bessie可以在图中随意走动,一旦棋盘满足条件游戏结束,则记录下结束时棋盘的状态

问最终Bessie可能可以走出多少种棋盘的状态来

标签

DFS

State Compression

思路(补)

\(3\times 3\)的棋盘共\(9\)个格子的状态通过状态压缩的方式用一个数字表示

\(0\)表示没有棋子,\(1\)表示放置了一个'M',\(2\)表示放置了一个'O'

所有状态数为\(3^9=19683\)

对于某个位置的状态的检查,可以预处理出\(3\)的次方幂方便直接进行调用,然后根据除法与取模获取状态


考虑深搜这张图,为防止重复搜索,设置vis数组标记每种状态是否已经搜索过

\(vis[i][j][k]\)表示走到位置\((i,j)\),当前棋盘状态为\(k\)

对于棋盘是否属于游戏结束的状态,直接暴力解压缩状态判断即可(共\(16\)种情况)

然后根据当前位置、棋盘状态进行搜索

如果当前位置需要放置棋子,并且棋盘状态中该位置没有棋子

将该棋子加入状态中,检查可行性,如果棋盘合法则结束本次搜索,将答案加入set中查重,否则继承当前状态继续向四周搜索即可

代码

//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/hash_policy.hpp>
#include<bits/stdc++.h>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define all(a) (a).begin(),(a).end()
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
using namespace std;
//using namespace __gnu_pbds;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
const ll mod=998244353;
const int dx[8]={0,1,0,-1,1,1,-1,-1},dy[8]={1,0,-1,0,1,-1,1,-1};
mt19937 mt19937random(std::chrono::system_clock::now().time_since_epoch().count());
ll getRandom(ll l,ll r){return uniform_int_distribution<ll>(l,r)(mt19937random);}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll qmul(ll a,ll b){ll r=0;while(b){if(b&1)r=(r+a)%mod;b>>=1;a=(a+a)%mod;}return r;}
ll qpow(ll a,ll n){ll r=1;while(n){if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}
ll qpow(ll a,ll n,ll p){ll r=1;while(n){if(n&1)r=(r*a)%p;n>>=1;a=(a*a)%p;}return r;}

int n;
char mp[30][90];
set<int> ans;
bool vis[26][26][20000];
int p3[10];

bool check(int sta)
{ //判断棋盘状态是否可以结束游戏
    int chess[3][3];
    repp(i,0,3)
        repp(j,0,3)
        {
            chess[i][j]=sta%3;
            sta/=3;
        }
    return chess[0][0]==1&&chess[0][1]==2&&chess[0][2]==2||
            chess[1][0]==1&&chess[1][1]==2&&chess[1][2]==2||
            chess[2][0]==1&&chess[2][1]==2&&chess[2][2]==2||
            chess[0][0]==1&&chess[1][0]==2&&chess[2][0]==2||
            chess[0][1]==1&&chess[1][1]==2&&chess[2][1]==2||
            chess[0][2]==1&&chess[1][2]==2&&chess[2][2]==2||
            chess[0][0]==1&&chess[1][1]==2&&chess[2][2]==2||
            chess[0][2]==1&&chess[1][1]==2&&chess[2][0]==2||
            chess[0][0]==2&&chess[0][1]==2&&chess[0][2]==1||
            chess[1][0]==2&&chess[1][1]==2&&chess[1][2]==1||
            chess[2][0]==2&&chess[2][1]==2&&chess[2][2]==1||
            chess[0][0]==2&&chess[1][0]==2&&chess[2][0]==1||
            chess[0][1]==2&&chess[1][1]==2&&chess[2][1]==1||
            chess[0][2]==2&&chess[1][2]==2&&chess[2][2]==1||
            chess[0][0]==2&&chess[1][1]==2&&chess[2][2]==1||
            chess[0][2]==2&&chess[1][1]==2&&chess[2][0]==1;
}

inline char getch(int x,int y,int z)
{ //获取(x,y)点的第z+1个字符
    return mp[x][(y-1)*3+z+1];
}

void dfs(int x,int y,int sta)
{
    //不合法情况或者已经搜索过
    if(getch(x,y,0)=='#'||vis[x][y][sta])
        return;
    vis[x][y][sta]=true;
    //如果走到了需要放置棋子的位置
    if(getch(x,y,0)=='M'||getch(x,y,0)=='O')
    {
        //获取这个棋子应该放置的位置(状压后)
        int pos=int(getch(x,y,1)-'1')*3+int(getch(x,y,2)-'1');
        //如果这个位置此前没有棋子
        if(sta/p3[pos]%3==0)
        {
            int val=(getch(x,y,0)=='M'?1:2);
            //加入这个棋子的状态
            sta+=val*p3[pos];
            if(!vis[x][y][sta])
            {
                vis[x][y][sta]=true;
                //检查状态,如果可以结束游戏则直接结束搜索
                if(check(sta))
                {
                    ans.insert(sta);
                    return;
                }
            }
        }
    }
    //向四周进行搜索
    repp(i,0,4)
        dfs(x+dx[i],y+dy[i],sta);
}

void solve()
{
    p3[0]=1;
    rep(i,1,9)
        p3[i]=p3[i-1]*3;
    cin>>n;
    rep(i,1,n)
        cin>>mp[i]+1;
    rep(i,1,n)
        rep(j,1,n)
            if(getch(i,j,0)=='B')
            {
                dfs(i,j,0);
                cout<<ans.size()<<'\n';
                return;
            }
}
int main()
{
    closeSync;
    //multiCase
    {
        solve();
    }
    return 0;
}



J - Portals

Link

题意

\(N\)个点,点\(v\)有四个传送门\(p_{v,1},p_{v,2},p_{v,3},p_{v,4}\),传送门是双向的,每个传送门连接两个点

假设你当前站在某个点的某个传送门的位置,以\((current\ vertex,\ current\ portal)\)描述你的位置

每次操作,你可以通过当前传送门传送到另一个顶点(改变顶点)

也可以保持顶点不动,换一个传送门(改变传送门)

传送门只能够在\(1,2\)或者\(3,4\)这两对编号中改变,即如果当前位于传送门\(1\),只能够改变到传送门\(2\)的位置,而不能直接改变到传送门\(3\)\(4\)的位置

你可以在顶点\(v\)上花费\(c_v\)来重新安排四个传送门的编号

要求最终让整张图所有点的所有传送门都连通,问最小花费

标签

Minimum Spanning Tree

思路

明显的,一开始的可以互相到达的联通块可以看作是一个环

初始时每对传送门之间由于可以互相到达,所以可以看作联通

或者一个传送门连接的两个点也可以互相到达,所以也可以看作联通

所以整张图刚开始就是一张存在多个环的图

要让整张图联通,实际上也就是要把所有环连成一个环;而重新安排传送门的编号的情况也只出现在同一个点的两对传送门位于两个不同的环中

此时交叉交换传送门编号,便可以通过花费\(c_v\)的代价使得两个环合并成一个环

换言之,这就是一个最小生成树

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

struct node
{
    int c,p[4];
    bool operator < (const node& a) const
    {
        return c<a.c;
    }
}ar[100050];

int n;
int gp[200050];

int fnd(int p)
{
    return p==gp[p]?p:(gp[p]=fnd(gp[p]));
}
void merge(int a,int b)
{
    gp[fnd(b)]=fnd(a);
}

void solve()
{
    cin>>n;
    rep(i,1,2*n)
        gp[i]=i;
    rep(i,1,n)
    {
        cin>>ar[i].c;
        repp(j,0,4)
            cin>>ar[i].p[j];
        merge(ar[i].p[0],ar[i].p[1]);
        merge(ar[i].p[2],ar[i].p[3]);
    }
    sort(ar+1,ar+n+1);
    int ans=0;
    rep(i,1,n)
    {
        int gx=fnd(ar[i].p[0]),gy=fnd(ar[i].p[2]);
        if(gx!=gy)
        {
            gp[gy]=gx;
            ans+=ar[i].c;
        }
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



K - United Cows of Farmer John I

Link

题意

定义一个二元组\((i,j),\ 1\le i\le j\le n\)是合法的,当且仅当\(i,j\)这两个位置所表示的数在区间\([i,j]\)中都仅出现一次

问合法的二元组数量

标签

Implementation

Data Structure : Binary Index Tree

思路

按顺序从左到右处理

假设当前处理到了\(i\)位置

定义\(last[j]\)表示在\([1,i]\)这段区间中,种类为\(j\)的牛最后出现的位置

如果将\(i\)定义为二元组的右端点,那么左端点一定是在这些\(last[j]\)中选出,满足了左端点在二元组区间中只出现一次的限制

又考虑到右端点的牛在区间中必须也只出现一次,所以左端点最小值应该是右端点的种类在\([1,i]\)倒数第二次出现的位置\(+1\)

所以在转移右端点时,只需要统计有多少个其他的\(last[j]\)的值在\([last[kind_i]+1,i]\)这段区间内即可

(vector直接二分模拟会TLE)

利用树状数组来维护区间中\(last[j]\)值的数量

如果\(last[j]=k\),那么就让树状数组中的\(k\)位置的值设置为\(1\)即可

每次询问树状数组中位置为\([last[kind_i]+1,i]\)这段区间中的和,即右端点为\(i\)的二元组数量

转移即将\(last[kind_i]\)设置为\(0\),再将\(i\)位置设置为\(1\)即可

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

const int maxn=200050;

struct BIT
{
    int a[maxn];
    void update(int p,int v){for(;p<maxn;p+=p&-p)a[p]+=v;}
    int query(int p){int r=0;for(;p;p-=p&-p)r+=a[p];return r;}
    int query(int l,int r){return query(r)-query(l-1);}
}tree;

int last[maxn];

void solve()
{
    int n;
    cin>>n;
    ll ans=0,kinds=0;
    rep(i,1,n)
    {
        int d;
        cin>>d;
        ans+=kinds-tree.query(last[d]); //全部已出现的种类数-last[d]之前的种类数和
        if(last[d])
            tree.update(last[d],-1); //消除上一次的影响
        else
            kinds++;
        last[d]=i;
        tree.update(last[d],1); //标记这个last[i]
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}


https://blog.csdn.net/qq_36394234/article/details/118765573

posted @ 2021-07-15 18:07  StelaYuri  阅读(180)  评论(0编辑  收藏  举报