2020牛客多校第三场题解(ABCEFGL)

题目链接

https://ac.nowcoder.com/acm/contest/5668

题解

A题Clam and Fish

题意:

题解:基于贪心的策略,如果有鱼的话,肯定钓鱼,因为如果制作鱼饵的话,后面还需要花时间用鱼饵捕鱼,而且也只能贡献一条鱼,所以肯定直接钓鱼比较优。之后考虑没有鱼的情况,分为有蛤蜊和无蛤蜊考虑,这里就有两种做法了。

1.当有蛤蜊时就直接制作鱼饵,否则的话就用鱼饵捕鱼,如果到最后还有x个鱼饵的话,就花费制作这x个鱼饵的一半时间x/2来捕鱼,这样钓的鱼的个数是最多的。

2.当有蛤蜊能够制作鱼饵时,需要考虑后面能否有足够的时间用鱼饵来捕鱼,如果有的话就制作鱼饵,否则的话就用之前已经制作的鱼饵来捕鱼。

做法一代码实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6+7;
char s[N];
int main(){
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        scanf("%s",s);
        int ans=0,t=0;
        for(int i=0;i<n;i++){
            if(s[i]=='2'||s[i]=='3') ans++;
            else if(s[i]=='1') t++;
            else if(t>0) ans++,t--;
        }
        printf("%d\n",ans+(t>0?t/2:0));
    }
    return 0;
}

做法二代码实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6+7;
char s[N];
int sum[N];
int main(){
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        for(int i=0;i<=n;i++) sum[i]=0;
        scanf("%s",s);
        int ans=0,t=0;
        for(int i=n-1;i>=0;i--) sum[i]=sum[i+1]+(s[i] == '0' || s[i] == '1');
        for(int i=0;i<n;i++){
            if(s[i]=='2'||s[i]=='3') ans++;
            else if(s[i]=='1'){
                if(sum[i]>t) t++;
                else ans++,t--;
            }
            else if(t>0) ans++,t--;
        }
        printf("%d\n",ans);
    }
    return 0;
}

B题Classical String Problem

题意

题解:官方题解讲的比较好,其实就是用个指针来模拟就行了。

代码实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6+7;
char s[N];
int main(){
    int q,sum=0;
    scanf("%s%d",s,&q);
    int len=strlen(s);
    while(q--){
        char opt[2];
        scanf("%s",opt);
        if(opt[0]=='M'){
            int x;scanf("%d",&x);
            sum+=x;
        }
        else{
            int id;scanf("%d",&id);id--;
            int res=id+sum;
            while(res<0) res+=len;
            printf("%c\n",s[res%len]);
        }
    }
    return 0;
}

C题Operation Love

题意:给你20个点,让你判断这20个点形成的是左手掌还是右手掌。

题解:因为点是逆时针或者顺时针给的,所以我们可以通过求相邻两点的距离(边长)来判断是顺时针还是逆时针,以及左手掌还是右手掌。

代码实现:

#include <bits/stdc++.h>
#define m_p make_pair
#define p_i pair<int, int>
#define _for(i, a) for (register int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (register int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
#define mem(a, b) memset(a, b, sizeof(a))
#define mem0(a) memset(a, 0, sizeof(a))
#define fil(a, b) fill(a.begin(), a.end(), b);
#define scl(x) scanf("%lld", &x)
#define sc(x) scanf("%d", &x)
#define abs(x) ((x) > 0 ? (x) : -(x))
#define PI acos(-1)
#define lowbit(x) (x & (-x))
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int maxn = 100005;
const int maxm = 1000005;
const int maxp = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const double eps = 1e-5;
const double E = 2.718281828;
int debug = 0;
 
typedef struct poi {
    double x, y;
    poi() {}
    poi(double x, double y) : x(x), y(y) {}
    void inp() { scanf("%lf%lf", &x, &y); }
    bool operator<(poi tem) {
        return x < tem.x || (x == tem.x && y < tem.y);
    }  //点排序-先排x再排y
    poi operator+(poi tem) { return poi(x + tem.x, y + tem.y); }  //点/向量 相加
    poi operator-(poi tem) { return poi(x - tem.x, y - tem.y); }
    poi operator*(double tem) { return poi(x * tem, y * tem); }
    double operator*(poi tem) { return x * tem.x + y * tem.y; }  //点积
    double operator^(poi tem) { return x * tem.y - y * tem.x; }  //叉积
    poi operator/(double tem) { return poi(x / tem, y / tem); }
    int dcmp(double x) {
        if (x < eps)
            return 0;
        else
            return x < 0 ? -1 : 1;
    }
    bool operator==(poi tem) {
        return dcmp(x - tem.x) == 0 && dcmp(y - tem.y) == 0;
    }
} Vector;
 
double Dot(Vector a, Vector b) { return a.x * b.x + a.y * b.y; }  //点积
double Length(Vector a) { return sqrt(Dot(a, a)); }               //长度
double Angle(Vector a, Vector b) {
    return acos(Dot(a, b) / Length(a) / Length(b));
}  //夹角
double Cross(Vector a, Vector b) { return a.x * b.y - a.y * b.x; }  //叉积
double Area(poi a, poi b, poi c) {
    return Cross(b - a, c - a) / 2;
}  //三点求三角形面积
Vector Rotate(Vector a, double rad) {
    return Vector(a.x * cos(rad), a.x * sin(rad) + a.y * cos(rad));
}  //向量旋转rad弧度
Vector Normal(Vector a) {
    double L = Length(a);
    return Vector(-a.y / L, a.x / L);
}  //单位向量--调用前请确保A不是零向量
double sgn(double x) { return fabs(x) - eps < 0 ? 0 : (x > 0 ? 1 : -1); }
Vector trunc(Vector a, double r) {  //化为长度为r的向量
    double L = Length(a);
    if (!sgn(L)) return a;
    r /= L;
    return Vector(a.x * r, a.y * r);
}
Vector rotleft(Vector a) { return Vector(-a.y, a.x); }   //逆时针旋转90度
Vector rotright(Vector a) { return Vector(a.y, -a.x); }  //顺时针旋转90度
Vector rotate(Vector a, const Vector p, double rad) {  // a绕着点p旋转rad弧度
    Vector v = a - p;
    double c = cos(rad), s = sin(rad);
    return Vector(p.x + v.x * c - v.y * s, p.y + v.x * s + v.y * c);
}
 
struct Line {
    poi s, e;
    Line() {}
    Line(poi &s, poi &e) : s(s), e(e) {}
};
 
double dist(poi a, poi b) {  //两点距离
    return sqrt((b - a) * (b - a));
}
 
double xmult(poi p0, poi p1, poi p2) {  // p0p1 X p0p2
    return (p1 - p0) ^ (p2 - p0);
}
 
bool Seg_inter_line(Line l1, Line l2) {  //判断直线l1和线段l2是否相交
    return sgn(xmult(l2.s, l1.s, l1.e)) * sgn(xmult(l2.e, l1.s, l1.e)) <= 0;
}
 
bool equ(double a, double b) {
    if(debug) cout << "fabs:" << fabs(a - b) << "\n";
    return fabs(a - b) < eps;
}
 
vector<poi> a;
int n = 20;
poi b[4];
 
void init() { a.resize(n); }
 
void getlr() {
    int l;
    _for(i, n) {
        if(debug) cout << dist(a[i], a[(i + n - 1) % n]) << "\n";
        if (equ(dist(a[i], a[(i + n - 1) % n]), 1)) {
            for (l = i; equ(dist(a[l], a[(l + n - 1) % n]), 1); l += 2, l %= n);
            l += n - 2;
            l %= n;
            _for(j, 4) b[j] = a[(l + j) % n];
            if (debug) _for(j, 4) cout << b[j].x << " " << b[j].y << "\n";
            return;
        }
    }
}
 
void sol() {
    init();
    _for(i, n) scanf("%lf%lf", &a[i].x, &a[i].y);
    getlr();
    if (dist(b[0], b[1]) > dist(b[2], b[3])) reverse(b, b + 4);
    if (debug) {
        cout << "reversed:\n";
        _for(j, 4) cout << b[j].x << " " << b[j].y << "\n";
    }
    double cr = (b[1].x - b[0].x) * (b[2].y - b[0].y) -
                (b[1].y - b[0].y) * (b[2].x - b[0].x);
    if (cr > 0) cout << "right\n";
    else cout << "left\n";
}
 
int main() {
    // ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifdef ONLINE_JUDGE
#else
    freopen("in.txt", "r", stdin);
    debug = 0;
#endif
 
    int T;
    while (cin >> T) {
        _for(i, T) { sol(); }
    }
    return 0;
}

E题Two Matchings

题意:

题解:

不难想到最优解就是排序后相邻的差值和。次优解的解法就有点麻烦了,因为可能四个或者六个形成次优解,所以这个时候就需要递推的去求1到n的次优解,即dp。

定义getV4(i)表示i到i+3的次优解,getV6(i)表示i到i+3的次优解,a数组为排序后的数组。

不难得到getV4(i)表示:   a[i+2]+a[i]+a[i+3]-a[i+1],getV6(i)表示: a[i+5]-a[i]+a[i+2]-a[i+1]+a[i+4]-a[i+3]。

状态:dp[i]表示从1到i的次优解。

状态转移方程:dp[i]=min(dp[i-3]+getV4(i-3),dp[i-5]+getV6(i-5))  {i>=9&&i&1)

即从1到i的次优解由 第1到i-3的次优解 + i-3到i的次优解 和 第1到i-5的次优解 + i-5到i的次优解 的最小值(最小次优解)形成。

边界条件:dp[3]=getV4(0),dp[5]=getV6(0),dp[7]=getV4(0)+getV4(4)。

详细证明和分析过程可以参考队友的博客:

代码实现:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5+7;
ll dp[N],a[N];
ll getV4(int id){
    return a[id+2]-a[id]+a[id+3]-a[id+1];
}
ll getV6(int id){
    return a[id+5]-a[id]+a[id+2]-a[id+1]+a[id+4]-a[id+3];
}
int main(){
    cin.sync_with_stdio(false);
    int T;cin>>T;
    while(T--){
        int n;cin>>n;
        for(int i=0;i<n;i++) cin>>a[i];
        sort(a,a+n);
        ll ans=0;
        for(int i=0;i<n;i+=2) ans+=a[i+1]-a[i];
        dp[3]=getV4(0);
        dp[5]=getV6(0);
        dp[7]=getV4(0)+getV4(4);
        for(int i=9;i<n;i+=2) dp[i]=min(dp[i-4]+getV4(i-3),dp[i-6]+getV6(i-5));
        cout<<ans+dp[n-1]<<endl;
    }
    return 0;
}

F题Fraction Construction Problem

题意:给你两个整数a和b,让你求出符合条件\frac{c}{d}-\frac{e}{f}=\frac{a}{b}\left ( d<b,f<b \right )的c,d,e,f。

题解:

当a和b不互质的时候,不难找到一组符合的解,定义g为a和b的最大公约数,答案为2*(a/g),b/g,a/g和b/g。

否则的话需要分为两种情况来考虑,b有小于等于一个相异质因子和b有大于一个相异质因子。

当b有小于等于一个相异质因子时(即b为1,质数,或者平方数,立方数这一类数时),答案是无解的。

证明过程如下图:

当b有大于一个相异质因子时,\small \frac{c}{d}-\frac{e}{f}=\frac{a}{b}可以化简为\small \frac{c*f-e*d}{d*f}=\frac{a}{b}

可以找到一组d和f,使得d*f=b且d和f互质,这样d和f就从未知量变成已知量了。

根据扩展欧几里得算法,已知a,b,我们可以求出满足\small a*x_{1}+b*x_{2}=gcd(a,b)的x1和x2。

因此问题就转换成了,已知d和f,我们可以求出 满足 \small c*f+e*d=gcd(d,f)=1 的e和c。

再把e和c同时扩大a倍,这样答案就求出来了。

因为求出的d和f不一定对应方程中真正的d和f,因此我们如果求出来的c>0,那么c表示真正的c,否则的话表示的是真正的e。

至于怎么想到这样求得呢?还是通过观察上面这个式子发现扩展欧几里得可以解决这样的问题(只要d和f确定),然后就不停往扩展欧几里得算法上凑就行了,虽然我没想到。

代码实现:

 

#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct Prime {
    vector<int> arr;
    int vis[2000006];
    void doit(int maxnum) {
        for(int i = 2; i <= maxnum; ++i) {
            if(!vis[i]) arr.push_back(i);
            int len=arr.size();
            for(int j = 0; j < len && arr[j] * i <= maxnum; ++j) {
                vis[arr[j] * i] = 1;
                if(i % arr[j] == 0) break;
            }
        }
    }
}P;
int quick_pow(int a,int b){
    int res=1;
    while(b){
        if(b&1) res=(a*res);
        b>>=1;
        a=(a*a);
    }
    return res;
}
ll ex_gcd(int a,int b,ll &x1,ll &y1)
{
	if(!b)
	{
		x1=1,y1=0;
		return a;
	}
	ll x2,y2;
	ll d=ex_gcd(b,a%b,x2,y2);
	x1=y2,y1=x2-(a/b)*y2;
	return d;
}
int main(){
    P.doit(2000000);
    cin.sync_with_stdio(false);
    int T;cin>>T;
    while(T--){
        int a,b,d,f;cin>>a>>b;
        int g=__gcd(a,b);
        if(g!=1){
            cout<<2*(a/g)<<" "<<b/g<<" "<<a/g<<" "<<b/g<<endl;
            continue;
        }
        if(b==1||!P.vis[b]){
            cout<<-1<<" "<<-1<<" "<<-1<<" "<<-1<<endl;
            continue;
        }
        int len=P.arr.size();
        int temp=b;
        for(int i=0;i<len;i++){
            if(temp%P.arr[i]==0){
                d=1;
                while(temp%P.arr[i]==0) d*=P.arr[i],temp/=P.arr[i];
                break;
            }
        }
        if(d==b){
            cout<<-1<<" "<<-1<<" "<<-1<<" "<<-1<<endl;
            continue;
        }
        f=b/d;
        ll c,e;
        ex_gcd(f,d,c,e);
        if(c>0) cout<<c*a<<" "<<d<<" "<<-e*a<<" "<<f<<endl;
        else cout<<e*a<<" "<<f<<" "<<-c*a<<" "<<d<<endl;
    }
    return 0;
}

G题Operating on a Graph

题意:

题解:不难发现一个点至多被侵略一次后,颜色就变成了和相邻点相同的颜色。

我们可以用G[i]表示这个颜色尚未侵略过的相邻点,对于每次操作的颜色,暴力的去遍历那些尚未侵略过的点,然后更新G[i]数组就行了,同时用并查集维护属于每个点的颜色。维护G[i]数组需要用链表O(1)合并,或者启发式合并。

启发式合并代码实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 8e5+7;
vector<int> G[N];
int fa[N];
int find(int x){
    if(x!=fa[x]) fa[x]=find(fa[x]);
    return fa[x];
}
int n,m;
void init(){
    for(int i=0;i<=n;i++){
        fa[i]=i;
        G[i].clear();
    }
}
int main(){
    cin.sync_with_stdio(false);
    int T;cin>>T;
    while(T--){
        cin>>n>>m;
        init();
        while(m--){
            int u,v;cin>>u>>v;
            G[u].push_back(v);
            G[v].push_back(u);
        }
        int q;cin>>q;
        while(q--){
            int u;cin>>u;
            if(fa[u]!=u) continue;
            vector<int> p=G[u];
            G[u].clear();
            int len=p.size();
            for(int i=0;i<len;i++){
                int v=find(p[i]);
                if(v!=u){
                    fa[v]=u;
                    if(G[u].size()<G[v].size()) swap(G[u],G[v]);
                    int L=G[v].size();
                    for(int j=0;j<L;j++) G[u].push_back(G[v][j]);
                }
            }
        }
        for(int i=0;i<n-1;i++) cout<<find(i)<<" ";
        cout<<find(n-1)<<endl;
    }
    return 0;
}
/*
5
4 3
0 1
1 2
2 3
4
0 1 3 0
4 3
0 1
1 2
2 3
2
0 2
4 3
0 1
1 2
2 3
2
0 3
4 1
1 3
1
2
5 5
0 1
0 2
1 2
1 3
3 4
3
4 4 0
*/

list模拟链表合并代码实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 8e5+7;
list<int> G[N];
int fa[N];
int find(int x){
    if(x!=fa[x]) fa[x]=find(fa[x]);
    return fa[x];
}
int n,m;
void init(){
    for(int i=0;i<=n;i++){
        fa[i]=i;
        G[i].clear();
    }
}
int main(){
    cin.sync_with_stdio(false);
    int T;cin>>T;
    while(T--){
        cin>>n>>m;
        init();
        while(m--){
            int u,v;cin>>u>>v;
            G[u].push_back(v);
            G[v].push_back(u);
        }
        int q;cin>>q;
        while(q--){
            int u;cin>>u;
            if(fa[u]!=u) continue;
            list<int> p=G[u];
            G[u].clear();
            for(list<int>::iterator it=p.begin();it!=p.end();it++){
                int v=find(*it);
                if(v!=u){
                    fa[v]=u;
                    G[u].splice(G[u].end(),G[v]);
                }
            }
        }
        for(int i=0;i<n-1;i++) cout<<find(i)<<" ";
        cout<<find(n-1)<<endl;
    }
    return 0;
}
/*
5
4 3
0 1
1 2
2 3
4
0 1 3 0
4 3
0 1
1 2
2 3
2
0 2
4 3
0 1
1 2
2 3
2
0 3
4 1
1 3
1
2
5 5
0 1
0 2
1 2
1 3
3 4
3
4 4 0
*/

L题Problem L is the Only Lovely Problem

签到题,不多说。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值