10.01练习赛总结

2021上海省赛 比赛链接:点击这里传送

A 题目链接:点击这里传送

在这里插入图片描述
思路:
输入两个向量,求出他们的叉乘。

#include<bits/stdc++.h>
using namespace std;


int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int x,y,z,xx,yy,zz;
    cin>>x>>y>>z;
    cin>>xx>>yy>>zz;
    int a=y*zz-yy*z;
    int b=xx*z-x*zz;
    int c=x*yy-xx*y;
    cout<<a<<" "<<b<<" "<<c<<endl;

    return 0;
}

B 题目链接:点击这里传送

在这里插入图片描述
思路:
显然 O ( n 3 ) O(n^3) On3的dp是不行的。
如果只考虑其中两种牌呢?
如果只有两类牌a和b,那么我们可以根据b-a的值进行倒序排序,这样就可以进行贪心(优先选b)
接着考虑对另一类牌c进行排序。
定义 d p [ i ] [ j ] dp\left[i\right]\left[j\right] dp[i][j]为总共选了i张牌,c类牌选了j张的情况( j < = i j<=i j<=i)
这样就分成了两种情况:

  • 选了c类牌 状态转移方程: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − 1 ] + c a r d [ i ] . c , d p [ i ] [ j ] ) dp[i][j]=max(dp[i-1][j-1]+card[i].c,dp[i][j]) dp[i][j]=max(dp[i1][j1]+card[i].c,dp[i][j])
  • 没选c类牌 此时选了谁的结果由之前贪心定义的排序顺序决定谁的优先级高 状态转移方程: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] + ? ? ? , d p [ i ] [ j ] ) dp[i][j]=max(dp[i-1][j]+???,dp[i][j]) dp[i][j]=max(dp[i1][j]+???,dp[i][j])

通过这样的方法,可以将复杂度降为 O ( n 2 ) O(n^2) O(n2)
需要特别注意的是,对于 d p [ i ] [ j ] dp[i][j] dp[i][j] j > i j>i j>i的部分是非法情况。而当i=j时, d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]会访问到非法情况,所以要对这些非法情况初始化为无穷小(理论上,这题是-1e9)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 5005
ll dp[MAXN][MAXN];//表示为前i次选择选了j次第三类,选了i-j次第一类和第二类
struct node
{
    ll a;
    ll b;
    ll c;
}card[MAXN];
ll n,a,b,c;
bool cmp(node a,node b)//排序后优先选b,因为b大的都被移到了前面
{
    if(a.b-a.a==b.b-b.a) return a.c>b.c;
    return a.b-a.a>b.b-b.a;
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n>>a>>b>>c;
    for(int i=1;i<=n;i++)
    {
        cin>>card[i].a>>card[i].b>>card[i].c;
    }
    for(int i=0;i<=n;i++)//dp[i-1][j] 会导致i<j的情况,必须初始化为无穷小(让它够小到无法更新dp[i][j]的值,因为这种i<j的情况是非法的,不可能存在的)
    {
        for(int j=i+1;j<=c;j++) 
        {
            dp[i][j]=-1e9;
        }
    }
    sort(card+1,card+1+n,cmp);
    for(int i=1;i<=n;i++)//第i个物品
    {
        for(int j=0;j<=min(i,(int)c);j++)//选了j个第三类物品
        {
            if(j>0) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+card[i].c);//选C
            if(i-j<=b) dp[i][j]=max(dp[i][j],dp[i-1][j]+card[i].b);//b还能继续更新
            else dp[i][j]=max(dp[i][j],dp[i-1][j]+card[i].a);
        }
    }
    cout<<dp[n][c]<<endl;
    return 0;
}

C 题目链接:点击这里传送

在这里插入图片描述
思路:
模拟。该咋说咋做。

#include<bits/stdc++.h>
using namespace std;
#define ZERO 1e-6

int n, m;
const int Max_n = 100 + 10;
struct node {
    int id, val;
};
node a[Max_n];

bool cmp(node x, node y){
    return x.id < y.id;
}

void solve(){
    cin >> n >> m;
    int suma = 0;
    for (int i=1;i<=n;++i){
        cin >> a[i].id >> a[i].val;
        suma += a[i].val;
    }
    double aver = 1.0*suma/n;
    for (int i=1;i<=n;++i){
        if (a[i].id == m){
            if(a[i].val<60)
            a[i].val = 60;
            continue;
        }
        if (a[i].val - aver > ZERO){
            a[i].val = max(0, a[i].val-2);
        }
    }
    sort(a+1, a+1+n, cmp);
    for (int i=1;i<=n;++i){
        printf(i==n?"%d\n":"%d ", a[i].val);
    }
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    solve();
    return 0;
}

D 题目链接:点击这里传送

在这里插入图片描述

思路:
先假设不用维护排与排之间的关系,就是单纯维护两个递增序列。
定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为一共i个人,第一排j个人,第二排i-j个人的组合数量
由于身高相同的人之间位置是可以相互交换的,就把所有身高相同的人缩为一个点。假设一共有x个这样的人,枚举去了第一排k个人,第二排x-k个人的所有合法情况,将这些情况的值累加起来就是答案。
现在再考虑之前忽视的条件。这个条件设置的很精妙,如果我们排序后优先把人放到第一排,这样就能够始终保证相同位置第一排的人不会比第二排的人高。
这样的话,我们得始终保证第一排的人大于等于第二排的人,即 j > = i − j j>=i-j j>=ij。将所有 j < i − j j<i-j j<ij的情况视为非法情况,在本题中意味着值为0,也就不用管它。
具体实现和细节看代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 5005
#define mod 998244353
ll n;
ll a[MAXN];
vector <ll> v;
ll cnt[MAXN];
ll order[MAXN];//预处理阶乘,最后要乘上顺序
ll dp[MAXN][MAXN];//dp[i][j]表示一共i个人,第一排放了j个人,第二排放了i-j个人的方案数量
void pre()
{
    order[0]=1;
    for(int i=1;i<=5000;i++) order[i]=order[i-1]*i%mod;
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n;
    pre();
    for(int i=1;i<=n;i++) 
    {
        cin>>a[i];
        cnt[a[i]]++;
    }
    sort(a+1,a+1+n);//排序后每次都先往第一排放,再往第二排放,一直保证第一排的人数不比第二排少,这样就可以完美避开题目的限制条件(因为后加的肯定比先加的高)
    for(int i=1;i<=n;i++)
    {
        if(cnt[i]>0) v.emplace_back(cnt[i]);
    }
    dp[0][0]=1;//初始状态
    ll now=0;//目前已经处理过的人,相当于i
    for(int i=0;i<v.size();i++)
    {
        ll x=v[i];//身高这么高的有x个人,缩点
        now+=x;
        for(int j=min(now,n/2);j>=now-j;j--)//第一排的人数,取min是因为j不能超过一半,j>=now-j就能保证第一排的人不比第二排的人少
        {
            for(int k=0;k<=x&&k<=j;k++)//分配给了第一排k个人
            {
                dp[now][j]=(dp[now][j]+dp[now-x][j-k])%mod;//状态转移方程,累加上一次第一排人数为j-k的情况
            }
        }

    }
    ll ans=dp[n][n/2];//这个结果只是组合的情况种数,由于身高相同的人可以随意站位,还要乘上顺序(阶乘)
    for(int i=1;i<=n;i++)
    {
        ans=(ans*order[cnt[i]])%mod;
    }
    cout<<ans<<endl;
    return 0;
}

E 题目链接:点击这里传送

在这里插入图片描述
思路:
算期望。根据输出可以得到概率,根据题意可以得到价值,相乘就完事了。

#include<bits/stdc++.h>
using namespace std;
double c[10]={0,-7,1,31,57,9977};//贡献
#define MAXN 105
char a[MAXN];
double b[MAXN];//概率
double p[MAXN];
double n,k;
double sum;
int main()
{
    //ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i]>>p[i];
        if(a[i]=='D') b[1]+=p[i];
        else if(a[i]=='C') b[2]+=p[i];
        else if(a[i]=='B') b[3]+=p[i];
        else if(a[i]=='A') b[4]+=p[i];
        else if(a[i]=='S') b[5]+=p[i];
       
    }
     for(int i=1;i<=5;i++)
        {
            sum=sum+b[i]*1.0*c[i]*1.0;
        }
        printf("%.4lf\n",sum*k);
    return 0;
}

G 题目链接:点击这里传送

在这里插入图片描述
思路:
预处理出一个前缀积,一个后缀积,相乘即可,记得取模。

#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define ll long long
#define mod 998244353
ll a[MAXN];
ll b[MAXN];ll c[MAXN];
ll n;


int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    b[0]=a[0];c[n-1]=a[n-1];
    for(int i=1;i<n;i++) b[i]=b[i-1]*a[i]%mod;
    for(int i=n-2;i>=0;i--) c[i]=c[i+1]*a[i]%mod;
    cout<<c[1]<<" ";
    if(n!=2)
    {
        for(int i=1;i<=n-2;i++)
        {
            cout<<b[i-1]*c[i+1]%mod<<" ";
        }
    }
    cout<<b[n-2];
    return 0;
}

H 题目链接:点击这里传送

在这里插入图片描述
思路:
我们可以对车祸发生的时间进行二分答案。
车祸发生有两种情况

  • 车祸正在发生 即:有两辆不同类型的车在当前时间位于同一位置上
  • 车祸已经发生 即:当前这辆车已经被后面的车赶上或者当前的车已经赶上了前面的车。

第一种类型非常好判断。
对于第二种类型,我们可以开两个数组 L [ 1 e 5 + 5 ] 和 R [ 1 e 5 + 5 ] L[1e5+5]和R[1e5+5] L[1e5+5]R[1e5+5]来记录初始状态各个车辆从左往右和从右往左的排列(如果类型相同且相邻,则定义为一类,因为他们相撞没事)。根据这两个数组我们就可以找到所有车辆一开始的相对位置。当某个时间段相对位置发生改变时,就说明已经发生了车祸。
具体实现和细节参考代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 100005
ll t,n,k,p,v,type;
struct node
{
    ll type;
    ll speed;
    ll pos;
}a[MAXN];
bool cmp(node a,node b){return a.pos<b.pos;}
ll L[MAXN],R[MAXN];//用于记录相对位置,如果前后两辆车类型相同则缩为一个点
struct c
{
    ll now_pos;//过了x秒后的位置
    ll index;//用于识别x秒后这是哪辆车
}car[MAXN];//存放x秒后车辆的状态
bool cmpp(c a,c b) {return a.now_pos<b.now_pos;}
bool check(ll x)//过了x秒后,查看当前状态有没有翻车
{
    for(int i=1;i<=n;i++)
    {
        car[i].now_pos=a[i].pos+a[i].speed*x;
        car[i].index=i;
    }
    sort(car+1,car+1+n,cmpp);
    for(int i=1;i<=n;i++)
    {
        if(i!=1&&car[i].now_pos==car[i-1].now_pos&&a[car[i].index].type!=a[car[i-1].index].type)//两辆车在同一位置且不是同一种类型
        {
            return false;
        }
        if(i<L[car[i].index]||i>R[car[i].index]) return false;//已经被前面的车追上或者追上了后面的车(早就车祸了)
    }
    return true;
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    
    cin>>n>>k;//车辆的数量和车辆的种类
    for(int i=1;i<=n;i++) cin>>a[i].pos>>a[i].speed>>a[i].type;
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++)//记录相对位置
    {
        if(a[i].type==a[i-1].type) L[i]=L[i-1];
        else L[i]=i;
    }
    for(int i=n;i>0;i--)//记录相对位置
    {
        if(a[i].type==a[i+1].type) R[i]=R[i+1];
        else R[i]=i;
    }
    ll l=0;ll r=2e9+100;ll ans=-1;
    while(l<=r)//对发生车祸的具体时间进行二分答案
    {
        ll mid=(l+r)/2;
        if(check(mid))//在这个时间段还未发生过车祸
        {
            l=mid+1;
        }
        else
        {
            r=mid-1;
            ans=mid-1;
        }
    }
    cout<<ans<<endl;
    

    return 0;
}

J 题目链接:点击这里传送

在这里插入图片描述
题意:
不考虑正负数。Alice和Bob的最佳策略就是每次拿走一个当前最大的卡片。
加入负数的话就套个绝对值,也一样。
根据正数和负数绝对值的和的大小决定先拿负数还是先拿正数。然后排序一下直接顺序拿走判断一下奇偶就行了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 5005
int a[5005];
ll z,f,sum1,sum2;//正数,负数,Alice,Bob
ll n;
bool cmp(int a,int b){return a>b;}
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(a[i]>0) z+=a[i];
        else if(a[i]<0) f+=a[i];
    }
    if(z>-f)
    {
        sort(a+1,a+1+n,cmp);
    }
    else sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
    {
        if(i&1==1) sum1+=a[i];
        else sum2+=a[i];
    }
    if(z>-f) cout<<sum1-sum2<<endl;
    else cout<<sum2-sum1<<endl;

    return 0;
}

K 题目链接:点击这里传送

在这里插入图片描述
思路:
很容易看出这是一道组合游戏的并。
将所有子游戏的sg值算出来,求出他们的异或和。如果异或和为0,后手胜;否则先手胜。这是组合游戏的并的定理。
x节点的SG值是去除x节点的后继值的SG值后最小的非负整数。由于这题没有规则,任意子串都是大串的后继,就把所有子串的sg值都求出来。
通过记忆化搜索的方式求sg值可以避免大量重复的计算。
在本题中很明显最终答案只和字符数量有关,与字符具体是啥无关。因此我们定义一个字符串并非看26个字母出现了几次,而是看各个字母出现个数的组合。两者是包含的关系,所以可以将多种情况合而为一加快程序运行时间。如aabb和ccdd是同一种情况。
具体实现方式和细节参考代码:

#include<bits/stdc++.h>
using namespace std;
//求出每种可能出现的字符串类型,把他们的sg值异或一下得到答案。大字符串可以分解为若干小字符串,这就是组合游戏的并。
//sg值定义:x节点的SG值是去除x节点的后继值的SG值后最小的非负整数。由于这题没有规则,任意子串都是大串的后继,就把所有子串的sg值都求出来
//通过记忆化搜索的方式求子串的sg值,避免重复计算
#define ll long long
#define MAXN 45
int t,n;
string s;
map<vector<int>,ll> remember;//记忆化搜索用,记录已经运算过的sg值
ll sg(vector<int> now)
{
    if(remember.count(now))//他妈的sg值可以为0,给老子卡了半天
    {
        return remember[now];
    }
    //取走一个字母的情况,并求出这些新字符串的sg值
    set <ll> check;//记录now的后继节点(子串)的sg值,sg值貌似挺大的,数组开不了
    for(int i=0;i<now.size();i++)
    {
        if(now[i]==0) continue;//这个字符未出现在字符串中
        now[i]--;
        vector <int> temp;
        for(int j=0;j<now.size();j++) temp.emplace_back(now[j]);//移走了一个字符,生成了一种子串    
        sort(temp.begin(),temp.end());//见64行注释
        check.insert(sg(temp));//求出这个子串的sg值
        //在移走一个字符的情况下,移走另一个不同的字符,并且记录下这个子串的sg值
        for(int j=i+1;j<now.size();j++)
        {
            if(now[j]==0) continue;
            now[j]--;
            vector <int> tmp;
            for(int k=0;k<now.size();k++) tmp.emplace_back(now[k]);
            sort(tmp.begin(),tmp.end());//见64行注释
            check.insert(sg(tmp));
            now[j]++;//删完了还原
        }
        now[i]++;//删完了还原
    }
    //x节点的SG值是去除x节点的后继值的SG值后最小的非负整数 上面已经求完了x所有后继值的SG值 现在只需找出这个非负整数并输出即可
    for(ll i=0;;i++)
    {
        if(!check.count(i)) return remember[now]=i;
    }
   
}

int main()
{   
    std::iostream::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>t;
    while(t--)
    {
        int xor_sg=0;//sg值的异或和
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>s;
            vector <int> cnt(26,0);//记录字母出现的个数
            for(int i=0;i<s.length();i++)
            {
                cnt[s[i]-'a']++;
            }
            sort(cnt.begin(),cnt.end());//点睛之笔,本题只和字符出现的个数有关,这样可以将多种情况缩为一点,如aabb和ccdd
            xor_sg=xor_sg^(sg(cnt));
        }
        if(xor_sg==0) cout<<"Bob"<<endl;
        else cout<<"Alice"<<endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值