"蔚来杯"2022牛客暑期多校训练营5补题 B, F, G, H, K, D, A

群友的题意https://docs.qq.com/doc/DVXJqWHZrRGtFbWR2

K Headphones 水题

题意:
image

代码:

#include <iostream>
#include <cstring>
#include <vector>
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define pii pair<char, int>
#define ull unsigned long long
using namespace std;
const int N = 1e6 + 10;
int n,m;
void solve()
{
    cin>>n>>m;
        int k=n-m;
        if(k>m)  {
            int ans=k+(m+1);
            cout<<ans<<endl;
        }
        else cout<<-1<<endl;

}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    while (t--)
        solve();
    return 0;
}

B Watches 二分

题意:
image

思路:
注意到答案具有单调性,考虑二分答案k
则问题变为判定是否能选择k件物品,总花费不超过M元,直接贪心即可

代码:

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define fir first
#define sec second

#define ins 0x3f3f3f3f
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
 
const int N = 1e5 + 10;
int a[N], b[N], n, m;
 
bool check(int mid){
    for(int i = 1; i <= n; i++){
        b[i] = a[i] + mid * i;
    }
    sort(b + 1, b + 1 + n);
    int sum = 0;
    for(int i = 1; i <= mid; i++){
        sum += b[i];
    }
    if(sum > m) return false;
    return true;
}
 
void solve(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    int l = 0, r = n;
    while(l < r){
        int mid = l + r + 1 >> 1;
        if(check(mid)){
            l = mid;
        }
        else r = mid - 1;
    }
    cout << l << endl;
}
 
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    solve();
    return 0;
}

H Cutting Papers 数学

题意:
image
思路:
根据不等式画出封闭区域,
image

直接计算圆形和阴影部分的并集
答案就是 (n/2)^2*(2+pi/2)

代码:

double ans, n;
void solve(){
    cin >> n;
    double r = n / 2;
    cout <<fixed << setprecision(9) << r * r * pi / 2 + r * r * 2;
}

G KFC Crazy Thursday 马拉车算法

题意:
给定一个字符串,问有多少个以K或者F或者C结尾的回文子串。
思路:
马拉车算法,求出len。
利用区间加法获得总和即可。

也就是(直接看代码更容易理解)对于新串在i处“+1”,在i+len[i]+1处“-1”。因为这个区间内的字符都有某个以他为结尾的回文串。
思路2
回文自动机模板题,初始化回文自动机之后,直接循环输入字符串统计回文子串的数量

代码:

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define pll pair<int, int>
#define ull unsigned long long
using namespace std;
const int N = 2e7 + 10;
char a[N], b[N];
int p[N];
int n;
void init()
{
    int k = 0;
    b[k++] = '$', b[k++] = '#';
    for (int i = 0; i < n; i++)
        b[k++] = a[i], b[k++] = '#';
    b[k++] = '^';
    n = k;
}
void manacher()
{
    int mr = 0, mid;
    for (int i = 0; i < n; i++)
    {
        if (i < mr)
            p[i] = min(p[mid * 2 - i], mr - i);
        else
            p[i] = 1;
        while (b[i - p[i]] == b[i + p[i]])
            p[i]++;
        if (i + p[i] > mr)
        {
            mr = i + p[i];
            mid = i;
        }
    }
}
int d[N];
int cnt[128];
void solve()
{
    cin>>n;
    cin >> a;
    init();
    manacher();

    for (int i = 0; i < n; i++)
    {
        int L = i, R = i + p[i] - 1;
        d[L]++;
        d[R + 1]--;
    }
    int pre = 0;
    for (int i = 0; i <n; i++)
    {
        pre += d[i];
        cnt[b[i]] += pre;
    }
    cout << cnt['k'] << " " << cnt['f'] << " " << cnt['c'] << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

F A Stack of CDs计算几何

原题:P2510 [HAOI2008]下落的圆盘

题意:
image

思路:
对于只有两个圆的时候,可以用余弦定理计算交点和圆形的连线和水平面的夹角。
对每个圆都搜索上面的所有这样的覆盖,计算每个盖住它的圆形的线段的左右端点,合并起来。
具体来说
对于一个圆,可以用-pi到pi的弧度表示区间,然后就可以用线段覆盖的方法求出区间被覆盖了多少了。 因此关键是对于两个圆盘i,j,如何求出i被覆盖的区间。
首先对于两个圆的圆心x,y,可以求出向量(y-x)的atan2值,相当于与x轴正方向的夹角α,范围-pi到pi。然后根据余弦定理\(cos∠A=(b^2+c^2-a^2)/2bc\),得到x的圆心到两圆的一个焦点的射线关于两圆连心线旋转角的余弦,然后用acos得到旋转角β,那么α±β就是对应的区间。
注意边界问题,如果下界<-pi,就变成下界+2pi->pi,上界>pi同理。还有要判断圆相离的情况。

最后计算没有被覆盖到的线段的长度。

代码:下落的圆盘

#include <bits/stdc++.h>
using namespace std;
const double pi = acos(-1.0);
const int N = 2005;
int n, cnt;
struct Point{
    double x, y;
    Point(){}
    Point(double x, double y) : x(x), y(y) {}
    bool operator <(const Point &rhs) const{
        return x < rhs.x;
    }
}s[N];
struct Circle{
    Point o;
    double r;
}a[N];
double sqr(double x){
    return x*x;
}
double getdis(Point a, Point b){
    return sqrt(sqr(b.x - a.x) + sqr(b.y - a.y));
}
void ins(double x, double y){
    s[++cnt] = Point(x, y);
}
void getsegment(Circle u, Circle v, double dis){
    double t1 = atan2(v.o.y - u.o.y, v.o.x - u.o.x);
    double t2 = acos((sqr(dis) + sqr(u.r) - sqr(v.r)) / (2*dis*u.r));
    Point t; t.x = t1 - t2; t.y = t1 + t2;
    if(t.x >= -pi && t.y <= pi) ins(t.x, t.y);
    else if(t.x < -pi){
        ins(t.x + 2*pi, pi);
        ins(-pi, t.y);
    }else{
        ins(t.x, pi);
        ins(-pi, t.y - 2*pi);
    }
}
double cal(){
    double ans = 0;
    double l = -10, r = -10;
    sort(s + 1, s + cnt + 1);
    for(int i = 1; i <= cnt; i++){
        if(s[i].x > r){
            ans += r - l;
            l = s[i].x;
            r = s[i].y;
        }
        else{
            r = max(r, s[i].y);
        }
    }
    ans += (r - l);
    return 2*pi - ans;
}
int main(){
    double ans = 0;
    scanf("%d", &n);
    for(int i = n; i >= 1; i--) scanf("%lf%lf%lf", &a[i].r, &a[i].o.x, &a[i].o.y);
    for(int i = 1; i <= n; i++){
        cnt = 0;
        int j;
        for(j = 1; j < i; j++){
            double tmp = getdis(a[i].o, a[j].o);
            if(a[j].r - a[i].r > tmp) break;
            if(a[j].r + a[i].r > tmp && fabs(a[j].r - a[i].r) < tmp){
                getsegment(a[i], a[j], tmp);
            }
        }
        if(j == i) ans += a[i].r * cal();
    }

    printf("%.3f\n", ans);
    return 0;
}

D Birds in the tree 树形dp

题意:
image

思路:
可以看出1的答案和0的答案是独立的;所以我们先求1的答案,再求0的答案。
用t表示这次我们所求的度数为1的节点的颜色。

我们定义dp[u] 为节点u和其的子树的符合条件的个数。
我们如何转移呢

首先想到的肯定是:
\(dp[u]=dp[v1]×dp[v2]×dp[v3]\)

但是因为对于下图
image

因为每个v的整个树都可以不选择。
就是下面这种情况:
4这棵子树都不要。
image

因此每个\(dp[v]\)需要+1。

又因为防止所有子树都不选择的情况,就是只剩下u节点。(因为只剩下u节点不一定是成立的,需要单独判断)
所以最后需要-1。

然后再单独判断u可不可以选。

因此\(dp[u]=(a[u]==t)(dp[v1]+1)×(dp[v2]+1)×(dp[v3]+1)-1\)

然后就是求\(ans\)
因为ans和dp的含义有点不同:
ans[u]每次需要加:在选择当前节点的前提下,有多少种选法。

dp[u]表示u及其子树有多少种选法。(u节点不一定选)。

大部分情况都是相同的,有一种情况就是
如果\(a[u]!=t\)并且u和单独一个v形成的树。如下图,很明显这种是错误的

因为u!=t又因为 \(x2==t\) , \(x1==t\) 所以u!=x1 ,u!=x2不符合题意。

image

所以\(ans[u]=dp[u] - \sum_{}^{}{dp[v_i]}\)

代码:

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define pll pair<int, int>
#define ull unsigned long long
using namespace std;
const int N = 3e5+10, M = 1e9+7;
int n;
int a[N];
int ans=0;
int dp[N],t;
vector<int> g[N];
void dfs(int u,int fa)
{
    int sum=0,mul=1;
    for(int v:g[u]){
        if(v==fa) continue;
         dfs(v,u);
        (sum+=dp[v])%=M;
        (mul*=(dp[v]+1))%=M;
    }

    (dp[u]=(a[u]==t)+mul-1+M)%=M;
    // cout<<dp[u]<<endl;
    if(a[u]==t)
    {
        (ans+=dp[u])%=M;
    }
    else (ans+=dp[u]-sum+M)%=M;
}
void solve()
{
    cin>>n;
    string s;
    cin>>s;
    for(int i=1;i<=n;i++){
        a[i]=s[i-1]-'0';
    }
    for(int i=0;i<n-1;i++){
        int x,y;cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    t=0;
    dfs(1,-1);
    t=1;
    memset(dp,0,sizeof dp);
    dfs(1,-1);
    cout<<ans%M<<endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

A Don’t Starve 普通DP

题意:
在二维平面上,有n个点有食物(每个点可以吃多次,每个点包含无限数量的食物,但一次只能取一个,离开后食物会刷新。)。
从原点出发,每次直线前往其他任意有食物的点,对与行走的规则是每一步都必须严格短于上一步。
最多可以收集到多少食物

思路

每次去到的下一个点的距离都要递减,但是如果每次都选择最长的,可能不是最优解,所以不是贪心,考虑DP。image

\(f[i][j]\)表示以i为父节点, j 还能拿走多少食物。
因为当前状态j,只与父节点和子节点有关。
得转移方程 \(f[i][j]=max(f[j][k])\) 其中,\(j->k\)这条路的长度小于\(i−>j\)的长度

而因为离开一个点后可以返回,所以可以反向dp,考虑倒推。先将所有边按长度从小到大排序,这样在考虑当前的路时,之前的路都是可以走的,
如果有长度相同通往同一个点的路,应该平行处理,因此还需要用\(s[i]\)记录上一次从\(u\)出发最远可以走多远。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e3+5;
int n;
int dp[N][N];
int x[N],y[N];
int s[N];
struct Node{
    int u,v;
    int w;
};
vector<Node> vec;
bool cmp(Node a,Node b)
{
    return a.w<b.w;
}
//2点间距离公式
inline int d(int u,int v)
{
    return (x[u]-x[v])*(x[u]-x[v])+(y[u]-y[v])*(y[u]-y[v]);
}
void solve()
{
    cin >> n;
     bool flag = false;
    for(int i=1;i<=n;i++){
        cin>>x[i]>>y[i];
        if (!x[i] && !y[i]) flag = true;
        vec.push_back({0,i,d(0,i)});
    }
    for(int i=1;i<n;++i){
        for(int j=i+1;j<=n;++j){
            vec.push_back({i,j,d(i,j)});
        }
    }
    //从小到大排序
    sort(vec.begin(),vec.end(),cmp);
    for(int i=0;i<vec.size();i++){
        int j=i;
       //将边权相同的边平行处理,因为题目要求严格递减
        while(vec[i].w==vec[j+1].w && j<vec.size())
            j++;
        //终点不能是0节点
        for(int k=i;k<=j;k++){
            int u=vec[k].u,v=vec[k].v;
            if(v) dp[u][v]+=s[v]+1;
            if(u) dp[v][u]+=s[u]+1;
        }

        for(int k=i;k<=j;k++){
           int u=vec[k].u,v=vec[k].v;
           if(u) s[u]=max(s[u],dp[u][v]);
           if(v) s[v]=max(s[v],dp[v][u]);;

        }
        i=j;

    }
    long long ans = 0;
    for (int i = 1; i <= n; ++i) 
        ans = max(ans, dp[0][i]);
    cout << ans + flag << endl;



}

signed main()
{
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

优化思路

我们可以设 \(dp_x\) 表示从点x出发后能到达的最多食物点。还是倒推,转移方程式为:\(dp_x=dp_y + 1\)

代码:

    //从小到大排序
    sort(vec.begin(), vec.end(), cmp);
    for (int l = 0, r; l < vec.size(); l = r)
    {
        r = l;
        while (vec[l].w == vec[r].w && r < vec.size())
            ++r;
        for (int j = l; j < r; j++)
        {
            int u = vec[j].u, v = vec[j].v;
            g[u] = dp[u], g[v] = dp[v];
        }

        for (int j = l; j < r; j++)
        {
            int u = vec[j].u, v = vec[j].v;
            g[u] = max(g[u], dp[v] + 1);
            if (u) //如果是0出发的点就不做反向处理
            {
                swap(u, v);
                g[u] = max(g[u], dp[v] + 1);
            }
        }
        for (int j = l; j < r; j++)
        {
            int u = vec[j].u, v = vec[j].v;
            dp[u] = g[u], dp[v] = g[v];
        }
    }
    cout << dp[0] + flag << endl;
}
posted @ 2022-08-03 15:23  kingwzun  阅读(60)  评论(0编辑  收藏  举报