“蔚来杯“2022牛客暑期多校训练营2

G构造 

Link with Monotonic Subsequence

题意:构造一个排列p,使max( LIS(p) , LDS(p) )最小

结论:排列权值的最小值为\left \lceil sqrt(n) \right \rceil

构造形如3 2 1 6 5 4 9 8 7的数列即可

#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ios std::ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define PII pair<int,int>
typedef long long ll;
const int N=1e6+10;
const int inf=0x3f3f3f3f;

using namespace std;
int n;
int a[N];
void solve()
{
    cin>>n;
    int k=sqrt(n);
    if(k*k!=n) k++;
    for(int i=1;i<=n/k;i++)
    {
        int tem=k*i;
        for(int j=(i-1)*k+1;j<=i*k;j++)
        {
            a[j]=tem;
            tem--;
        }
    }
    int tem=n;
    for(int i=n/k*k+1;i<=n;i++)
    {
        a[i]=tem;
        tem--;
    }
    for(int i=1;i<=n;i++) cout<<a[i]<<" \n"[i==n];
}
int main()
{
    //ios;
    int _t=1;
    cin>>_t;
    while(_t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

J数学|线性回归方程

Link with Arithmetic Progression

题意:

思路:新数列y即为y=bi+a;

用回归方程y=bi+a拟合各点(i,ai),此时回归直线上的点到原数列点差平方总和为最小

#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ios std::ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define PII pair<int,int>
typedef long long ll;
const int N=1e5+10;
const int inf=0x3f3f3f3f;

using namespace std;
int n;
int arr[N];
void solve()
{
    scanf("%d",&n);
    double _x=(n+1)/2.0;
    double _y=0;
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&arr[i]);
        _y+=arr[i];
    }
    //for(int i=1;i<=n;i++) cout<<arr[i]<<'\n';
    _y/=(double)n;
    double b=0;
    double tem=0;
    for(int i=1;i<=n;i++)
    {
        b+=(double)(i-_x)*(arr[i]-_y);
        tem+=(double)(pow(i-_x,2));
    }
    b/=(double)tem;
    double a=1.0*_y-b*_x;
    double ans=0;
    for(int i=1;i<=n;i++)
    {
        ans+=(double)pow(1.0*arr[i]-(1.0*b*i+a),2);
    }
    printf("%.8f\n",ans);
}
int main()
{
    //ios;
    int _t=1;
    scanf("%d",&_t);
    while(_t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

K动态规划

Link with Bracket Sequence I

题意:已知长度为n的括号序列a,a是长度为m合法括号序列b的子序列,问b的数量

思路:f[i][j][k]表示在序列 b 的前 i 位中,与 a 的 lcs 为 j ,且左括号比右括号多 k 个的方案数。

在每一个状态后面我们可以选择添加左括号或是右括号,根据给定序列的j+1位是否匹配来决定转移的方式,在添加右括号时需要注意右括号的数量不能超过左括号。

#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ios std::ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define PII pair<int,int>
typedef long long ll;
const int N=210,p=1e9+7;
const int inf=0x3f3f3f3f;

using namespace std;
int n,m;
int f[N][N][N];
void solve()
{
    cin>>n>>m;
    string s;
    cin>>s;
    s='0'+s;
    memset(f,0,sizeof f);
    f[0][0][0]=1;
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<=n;j++)
        {
            for(int k=0;k<=i;k++)
            {
                if(s[j+1]=='(') f[i+1][j+1][k+1]=(f[i+1][j+1][k+1]+f[i][j][k])%p;
                else f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k])%p;
                if(k)
                {
                    if(s[j+1]==')') f[i+1][j+1][k-1]=(f[i+1][j+1][k-1]+f[i][j][k])%p;
                    else f[i+1][j][k-1]=(f[i+1][j][k-1]+f[i][j][k])%p;
                }
            }
        }
    }
    cout<<f[m][n][0]<<'\n';
}
int main()
{
    //ios;
    int _t=1;
    cin>>_t;
    while(_t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

D找负环

Link with Game Glitch

题意:n种物品,m种物品间的转换方法,每k*ai个物品bi可以换成k*ci个物品di。求最大的w,使得不存在得到无限物品的方法。考虑建图bi到di权值为(w*ci)/ai的有向边。即不存在边权乘积大于1的环

思路:二分w,double有可能溢出,因此要取log,用spfa判断负环。

#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ios std::ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define PII pair<int,int>
typedef long long ll;
const int N=2010;
const int inf=0x3f3f3f3f;

using namespace std;
int n,m;
int h[N],e[N],ne[N],idx;
double w[N];
void add(int a,int b,double ww)
{
    e[idx]=b,w[idx]=ww,ne[idx]=h[a],h[a]=idx++;
}
int cnt[N];
bool st[N];
double dis[N];
double ww;
bool check()
{
    memset(dis,0,sizeof dis);
    memset(cnt,0,sizeof cnt);
    memset(st,0,sizeof st);
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        q.push(i);
        st[i]=1;
    }
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        st[t]=false;

        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dis[j]<dis[t]+w[i]+ww)
            {
                dis[j]=dis[t]+w[i]+ww;
                if(!st[j])
                {
                    q.push(j);
                    st[j]=true;
                }
                if(++cnt[j]>=n) return true;
            }
        }
    }
    return false;
}
void solve()
{
    memset(h,-1,sizeof h);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(b,d,(double)log(c)-log(a));
    }
    double l=0,r=1,mid;
    while(r-l>1e-8)
    {
        mid=(l+r)/2;
        ww=log(mid);
        if(check()) r=mid;
        else l=mid;
    }
    printf("%.8f\n",l);
}
int main()
{
    //ios;
    int _t=1;
    //cin>>_t;
    while(_t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

L滚动数组

Link with Level Editor I

题意:n个世界,每个世界是m个点的有向图,从这些世界中选连续的几个世界进行游戏,开始位于点1,在每个世界中,可以选择不动或经过一条边,然后传送到下一个世界的该点上,当无法传送时游戏结束。当游戏结束时位于m点获胜。问最少选几个连续的世界可以获胜

思路:对于每个世界i,枚举边u -> v

到达点v有两种途径(1)从i-1世界的点v到 i 世界的点v

                               (2)从i-1世界的点u到 i 世界的点u,再用u->v,从u到v

因为我们要求1到m穿越的世界个数,所以我们记录到达点u的最近点1所在的世界编号

我们用滚动数组now表示i世界,pre表示i-1世界

对于u- >v,要更新now[v]=max(now[v] , pre[u]) (越大的越近)

当v==m时,更新答案ans=min(ans,i-now[v]+1)

#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ios std::ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define PII pair<int,int>
typedef long long ll;
const int N=2e3+10;
const int inf=0x3f3f3f3f;

using namespace std;
int n,m;

void solve()
{
    cin>>n>>m;
    vector<int>now(m+1,-inf),pre(m+1,-inf);//到达点u的最近点1所在的世界编号
    int ans=inf;
    for(int i=1;i<=n;i++)
    {
        int l;
        cin>>l;
        pre[1]=i;
        for(int j=1;j<=l;j++)
        {
            int u,v;
            cin>>u>>v;
            now[v]=max(now[v],pre[u]);
            if(v==m) ans=min(ans,i-now[v]+1);
        }
        pre=now;
    }
    if(ans>n) cout<<-1<<'\n';
    else cout<<ans<<'\n';
}
int main()
{
    //ios;
    int _t=1;
    //cin>>_t;
    while(_t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

C Nim游戏

Link with Nim Game

题意:n堆石子,两人轮流从任一堆中取石子,不能取的人输。当异或和不为0时,先手必胜,否则先手必败。赢的人想要尽快结束游戏,输的人想要尽慢结束,问游戏轮数和第一轮中先手可采取的方案数。

思路:如果先手是必胜态那么方案就是他第一次能拿最多的方案数。如果先手必败,那么两个人必然一个一个的拿,对于方案数需要判断拿一个石头后下一步是否也必须拿一个石头。

先手必胜时,先手一定尽量多拿使得剩余石子异或和为0,如何求能拿多少?假设对a[i]取,取后变为x,使得sum^a[i]^x=0,求得x;那么拿取数量为a[i]-x;

先手必败时,无论先手怎么拿,对手遇到的永远xor不为0,所以先手只拿1个,但是我们不能让对手有拿很多个石头也能让异或和变为0的状态,也就是说我们需要在一个堆拿掉一个石头,我们记录每个堆拿掉一个石头剩下的异或和放入set去重,然后我们再转化为刚才分析完的必胜态的状态,找到使得异或和=0的取值,也就是sum^a[i]^x = 0,如果取出的值可以大于1,那么对手可能不拿1个而是拿很多个从而结束游戏。因此我们只需要找到哪些方案拿完一个石头接下来必拿一个石头的种类即可。

#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ios std::ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define PII pair<int,int>
typedef long long ll;
const int N=1e5+10;
const int inf=0x3f3f3f3f;

using namespace std;
int n;
ll a[N];
void solve()
{
    cin>>n;
    ll sum=0,tot=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sum^=a[i];
        tot+=a[i];
    }
    if(sum)//先手必胜
    {
        ll maxv=0,ans=0;
        for(int i=1;i<=n;i++)
        {
            ll t=a[i]-(sum^a[i]);
            maxv=max(maxv,t);//第一步可取的最大石子数
        }
        for(int i=1;i<=n;i++)
        {
            ll t=a[i]-(sum^a[i]);
            if(t==maxv) ans++;//方案数
        }
        cout<<tot-maxv+1<<' '<<ans<<'\n';
    }
    else
    {
        set<int>S,T;
        for(int i=1;i<=n;i++)
        {
            S.insert(sum^a[i]^(a[i]-1));//在a[i]中拿走1个后的异或和
        }
        for(auto x:S)
        {
            for(int i=1;i<=n;i++)
            {
                ll t=sum^a[i]^x;
                if(a[i]-t>1) T.insert(x);
            }
        }
        for(auto it:T) S.erase(it);
        ll ans=0;
        for(int i=1;i<=n;i++) 
        {
            if(S.count(a[i]^(a[i]-1))) ans++;
        }
        cout<<tot<<' '<<ans<<'\n';
    }
}
int main()
{
    //ios;
    int _t=1;
    cin>>_t;
    while(_t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值