2023牛客寒假算法基础集训营6_20230203「预处理」「最小生成树+小结论」「优化模拟」「博弈」「容斥」

7/12

好多典,不会,坏。

已过非太水的题们

//C
//https://ac.nowcoder.com/acm/contest/46814/C

//贪心小典

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 1000000007
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int arr[N];
int b[N];

void work() {
    int n;cin>>n;
    for(int i=1;i<=n/2;i++){
        if(i&1){
            arr[i]=i*2-1;
            arr[n-i+1]=i*2;
        }else{
            arr[n-i+1]=i*2-1;
            arr[i]=i*2;
        }
    }
    if(n&1){
        arr[n/2+1]=n;
    }
    
    for(int i=1;i<=n;++i){
        b[i]=arr[i];
    }
    
    int ans=0;
    for(int i=n;i>=1;--i){
        for(int j=1;j<i;++j){
            arr[j]+=arr[j+1];
            arr[j]%=mod;
        }
    }
    arr[1]%=mod;
    cout<<arr[1]<<'\n';
    for(int i=1;i<=n;++i){
        cout<<b[i]<<" \n"[i==n];
    }
}

signed main() {
	io;
	work();
	return 0;
}
//F
//https://ac.nowcoder.com/acm/contest/46814/F

//优先队列预处理

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 1e9 + 10;
int arr[200005];
//int ans[N];

void work() {
    int n,q;cin>>n>>q;
    
    priority_queue<int>pq;
    for(int i=1;i<=n;++i){
        cin>>arr[i];
        pq.push(arr[i]);
    }
    int l=1;
    map<int,int>ans;
    while(pq.top()!=1ll&&(!pq.empty())){
        int t=pq.top();pq.pop();
        int a=__builtin_popcount(t);
        pq.push(a);
        ans[l]=pq.top();
        l++;
        if(l>1e9){
            break;
        }
    }
    
    while(q--){
        int k;cin>>k;
        if(!ans[k]){
            cout<<"1\n";continue;
        }else cout<<ans[k]<<'\n';
    }
    
}

signed main() {
	io;
	work();
	return 0;
}
//I
//https://ac.nowcoder.com/acm/contest/46814/I

//诈骗bfs,感觉这次写的小而美

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
vector<int>a[N];
bool vis[N];
map<pii,int>mp;
int n,m;
int minn;

void bfs(int num){
    queue<pii>q;
    vis[num]=1;
    q.push(m_p(num,0));
    while(!q.empty()){
        pii t=q.front();q.pop();
        int x=t.first,y=t.second;
        if(x==n){
            minn=y;
        }
        for(auto to:a[x]){
            if(!vis[to]){
                q.push(m_p(to,y+1));
                vis[to]=1;
            }
        }
    }
}

void work() {
    cin>>n>>m;
    for(int i=1;i<=m;++i){
        int u,v,w;
        cin>>u>>v>>w;
        mp[m_p(u,v)]=w;
        mp[m_p(v,u)]=w;
        a[u].pb(v);
        a[v].pb(u);
    }
    if(a[1].size()>1){
        bfs(1);
        cout<<minn<<'\n';
    }else{
        int to=a[1][0];
        bfs(to);
        if(m>minn+1){
            cout<<minn+1<<'\n';
        }
        else{
            int ans=mp[m_p(1,to)];
            ans+=minn;
            cout<<ans<<'\n';
        }
    }
}

signed main() {
	io;
	work();
	return 0;
}
//D
//https://ac.nowcoder.com/acm/contest/46814/D

//字符串小典

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int pre[N],suf[N];
int sum[N],c[N],f[N];

void work() {
    string s;cin>>s;
    int n=s.length();
//法1
    s=' '+s;
    for(int i=1;i<=n;++i){
        pre[i]=pre[i-1]+(s[i]=='u'?1:0);
    }
    for(int i=n;i>0;--i){
        suf[i]=suf[i+1]+(s[i]=='u'?1:0);
    }
    
    int ee=0,idx=-1;
    for(int i=1;i<=n;++i){
        if(s[i]=='d'){
            c[1]+=suf[i];
            c[i]-=suf[i];
            c[i+1]+=pre[i];
            f[i]=pre[i]*suf[i];
        }
    }
    
    for(int i=1;i<=n;++i){
        sum[i]=sum[i-1]+c[i];
        if(s[i]=='u') f[i]=sum[i];
    }
    for(int i=1;i<=n;++i){
        if(f[i]>ee){
            ee=f[i];
            idx=i;
        }
    }
    
    if(idx!=-1){
        s[idx]='a';
    }
    s.erase(s.begin(),s.begin()+1);
    cout<<s<<'\n';
//法2
//只需要比较最左最右的u和所有d即可。贪心地,最左or最右的u一定是贡献最大的。
    /*
    vector<int>d;
    int l=0,r=n-1;
    bool f1=0,f2=0;
    for(int i=0;i<n;++i){
        if(s[i]=='u'){
            pre[i]++;
            if(f1==0){
                l=i;f1=1;
            }
        }
        if(s[i]=='d'){
            d.pb(i);
        }
    }
    for(int i=n-1;i>=0;--i){
        if(s[i]=='u'){
            suf[i]++;
            if(f2==0){
                f2=1;r=i;
            }
        }
    }
    for(int i=1;i<n;++i){
        pre[i]+=pre[i-1];
    }
    for(int i=n-2;i>=0;--i){
        suf[i]+=suf[i+1];
    }
    if(d.size()==0){
        cout<<s<<'\n';
        return;
    }
    int ddd=0;
    int pru=0,suu=0;
    int mid=0;
    for(int i=0;i<d.size();++i){
        int to=d[i];
        int res=pre[to]*suf[to];
        if(res>ddd){
            mid=to;
            ddd=max(ddd,res);
        }
        
        if(to>l){
            pru+=suf[to];
        }
        if(to<r){
            suu+=pre[to];
        }
    }
    
    if(ddd>pru){
        if(ddd>suu){
            s[mid]+=1;
        }else{
            s[r]+=1;
        }
    }else{
        if(pru>suu){
            s[l]+=1;
        }else{
            s[r]+=1;
        }
    }*/
}

signed main() {
	io;
	work();
	return 0;
}

B.

B-阿宁的倍数_2023牛客寒假算法基础集训营6 (nowcoder.com)

思路:

预处理因数。有点集训营2的感觉。

 处理出来数据范围内所有数的因子,在输入的时候根据处理出的数组索引去记录下标,询问时查找的就是当前数在an的倍数个数-在当前数的倍数个数。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 2e5 + 50;
int arr[2*N];

void work() {
    int n,q;
    cin>>n>>q;
    vector<int>fac[N],id[N];
    //令fac[n]储存n的所有因子
    for(int i=1;i<N;++i){
        for(int j=1;j*i<N;++j){
            fac[i*j].pb(i);
        }
    }
    //id[x]储存x的所有倍数的下标
    for(int i=1;i<=n;++i){
        scanf("%lld",&arr[i]);
        for(auto x:fac[arr[i]]){
            id[x].pb(i);
        }
    }
    while(q--){
        int op;int num;
        scanf("%lld",&op);
        if(op==1){
            scanf("%lld",&arr[++n]);
            for(auto y:fac[arr[n]]){
                id[y].pb(n);
            }
        }else{
            scanf("%lld",&num);
            int tmp=upper_bound(id[arr[num]].begin(),id[arr[num]].end(),num)-id[arr[num]].begin();
            int ans=id[arr[num]].size()-tmp;
            printf("%lld\n",ans);
        }
    }
}

signed main() {
	//io;
	work();
	return 0;
}

 E.

E-阿宁的生成树_2023牛客寒假算法基础集训营6 (nowcoder.com)

思路:

小结论。(感觉不配用数论)

易知在k+1及之后的数和1取gcd时会有最小路径。

在1->k间暴力遍历找gcd,取最小,和与1的lcm(即当前值)比较取最小。

有关__gcd(),两个数据类型必须相同且是int or ll,浮点型不可,传过去什么值就传回来什么值。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数

void work() {
    int n,k;cin>>n>>k;
    int num=min(n,k+1);
    int ans=n-num;
    for(int i=2;i<=num;++i){
        int minn=i;
        //如果没有互素的数就和1取lcm最大为i
        for(int j=i+1+k;j<=n;++j){
            minn=min(minn,__gcd(i,j));
            if(minn==1ll) break;
        }
        ans+=minn;
    }
    cout<<ans<<'\n';
}

signed main() {
	io;
	work();
	return 0;
}

J.

J-阿宁指指点点_2023牛客寒假算法基础集训营6 (nowcoder.com)

思路:

小模拟题,有点读不懂(晕。

如果直接暴力模拟会t,优化需要做到把rank榜对的全部更新变成对某一个人的更新。打算过一阵子重新看题自己再写一写,有点不会。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 2e4 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
ll ans[N];
int tot=0;
map<string,int>mp;
vector<int> a[N],cha[N];//a记录做题时间,cha记录爬取时间
vector<ll> b[N];//记录前缀和

struct node{
    int op,t;
    string name;
    int w,id;
    friend bool operator <(node x,node y){
        return x.t<y.t;
    }
}q[N];

void work() {
    int n,T,R;cin>>n>>T>>R;
    for(int i=1;i<=n;++i){
        cin>>q[i].op>>q[i].t>>q[i].name;
        if(q[i].op==2){
            cin>>q[i].w;
            ans[i]=-1;
        }
        q[i].id=i;
        if(!mp[q[i].name]) mp[q[i].name]=++tot;
    }
    sort(q+1,q+1+n);
    for(int i=1;i<=n;++i){
        int id=mp[q[i].name];//当前人
        int t=q[i].t;//当前时间
        if(q[i].op==1){
            //找当前人被爬的最晚时间
            int k=upper_bound(cha[id].begin(),cha[id].end(),t)-cha[id].begin()-1;
            int time=t/T*T;
            if(k>=0) time=max(time,cha[id][k]);//当前人rank榜最后更新时间
            //找当前人做题最后时间
            k=upper_bound(a[id].begin(),a[id].end(),time)-a[id].begin()-1;
            ll sum=0;
            //查到最大前缀和
            if(k>=0) sum=b[id][k];
            ans[q[i].id]=sum;
            cha[id].pb(t+R);//在R时刻之后才可以更新
        }else{
            ll sum=q[i].w+(b[id].size()?b[id].back():0);
            a[id].pb(t);
            b[id].pb(sum);
        }
    }
    for(int i=1;i<=n;++i){
        if(ans[i]!=-1) cout<<ans[i]<<'\n';
    }
}

signed main() {
	io;
	work();
	return 0;
}

K.

K-阿宁大战小红_2023牛客寒假算法基础集训营6 (nowcoder.com)

思路:

是魔法一样的博弈啊,谢谢群友教我。

首先我们可以小范围暴力手玩博弈,然后去递推。 

暴力注释写的很清楚了,可以画数轴帮助理解,后手必胜的递推也很显然。下面主要说说先手必胜的递推。

(*):以下1代表先手胜。

我们首先要知道2是不能连续的,因为如果i是后手必胜,i-1一定是先手必胜,先手可以+1让自己变成i状态的后手。

假设一个j是2,那么j-1 , j+1 , 2j  , 2j+1一定都是1,那么我们现在看2j-1,他无论是加还是除都是1,所以他一定是先手必败态即2。现在我们看2j-2,他必是一个1。现在看j-1(1)就可递推出来了先手必胜的转移形式。

j-1jj+12j-22j-12j2j+1
1211211
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
bool vis[N];
int f[N];
int x;

int dfs(int now,int who){
    if(vis[now]) return (who==0)+1;
    else if(now>=2*x) return 3;
    //如果now比2x还要大就说明加过头了不能再除了就得一直加,平局
    vis[now]=1;
    int r1=dfs(now+1,who^1);
    int r2=dfs(now/2,who^1);
    vis[now]=0;
    if(who==1){
        if(r1==1||r2==1)return 1;
        else if(r1==2&&r2==2)return 2;
        else return 3;
    }else{
        if(r1==1&&r2==1)return 1;
        else if(r1==2||r2==2) return 2;
        else return 3;
    }
    //上面判断的大意是,如果当前人的一个操作可以使自己必胜那自己必胜
    //如果每个操作都是对方胜那对方必胜
    //否则无法分出胜负
}

void work() {
    cin>>x;
    mem(vis,0);
    vis[0]=1;
    for(int i=1;i<=40;++i){
        f[i]=dfs(i,1);//dfs内: 1代表先手,0代表后手&&dfs的return值==fx
        //f[x]: 1代表先手赢,2代表后手赢,3代表平局
    }
    for(int i=20;i<=500;++i){
        if(f[i]==1){
            //(*)
            f[2*i]=1;f[2*i+1]=2;
        }else if(f[i]==2){
            f[2*i]=1;f[2*i+1]=1;
        }
    }
    if(f[x]==1)cout<<"ning\n";
    else if(f[x]==2)cout<<"red\n";
    else cout<<"draw\n";
}

signed main() {
	io;
	work();
	return 0;
}

L.

L-阿宁睡大觉_2023牛客寒假算法基础集训营6 (nowcoder.com)

思路:

容斥||dp,感觉本质类似。

容斥特性是奇数加偶数减。

这样写的像一个很容斥的dp啊。排序过后,我们先对每一个噩梦点求1,1到达的方案数,然后去递推,每两次遇到一个之前的点,-就会变成+(-(-))。这里f[i]表示的是当前噩梦格子是走过的第一个噩梦的所有方案数。

(有点抽象,举个例子,1是必经过1的路径数,2=2‘- (1->2),3=3'- (1->3) - (2->3)=3'- (1->3) - (2'->3 -(1->2->3))=3'- (1->3) - (2'->3) + (1->2->3)。大概酱紫吧。

最后再求出来从当前格子走到结尾的每种方法。

相当于我们把路径拆开了去计算。(感觉容斥也有点魔法的啊

另:注释掉的是快速幂求组合数的方法,线性逆元预处理快了不到十倍,但是不太懂为什么只能用多少处理多少,试着修改了2*n,3*n范围越大越不对。

----->2023.2.21更新:气乐了,是因为数组越界,我怎么总写愚蠢东西啊。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 1000000007
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
pii a[15];
ll fac[N],inv[N];
ll f[N];
int n,m;

ll C(int a,int b){
    if(a<b or a<0 or b<0)
    return 0;
    return 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;
}

/*ll qpow(ll a, ll b = mod - 2, int p = mod) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p; b >>= 1;
    }
    return res;
}

int C(int n, int m) {
    return fac[n] * qpow(fac[n - m] * fac[m] % mod) % mod;
}*/

void init(){
    fac[0]=fac[1]=inv[0]=inv[1]=1;
    //for (int i = 1; i <= 2 * n; i++) fac[i] = fac[i - 1] * i % MOD;
	for(int i=2;i<=n;++i){
        fac[i]=1ll*fac[i-1]*i%mod;
        inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
    }
    for(int i=2;i<=n;++i){
        inv[i]=1ll*inv[i]*inv[i-1]%mod;
    }
}

void work() {
    cin>>n>>m;
    init();
    for(int i=1;i<=m;++i){
        cin>>a[i].fi>>a[i].se;
    }
    sort(a+1,a+1+m);
    for(int i=1;i<=m;++i){
        f[i]=C(a[i].fi+a[i].se-2,a[i].fi-1);
        for(int j=1;j<i;++j){
            if(a[j].fi<=a[i].fi&&a[j].se<=a[i].se){
                f[i]=(f[i]-f[j]*C(a[i].fi-a[j].fi+a[i].se-a[j].se,a[i].fi-a[j].fi)%mod)%mod;
            }
        }
    }
    ll res=0;
    for(int i=1;i<=n;++i){
        int x=i,y=n-i+1;
        res=(res+C(n-1,x-1))%mod;
        for(int j=1;j<=m;++j){
            if(a[j].fi<=x&&a[j].se<=y){
                res=(res-f[j]*C(x-a[j].fi+y-a[j].se,x-a[j].fi)%mod)%mod;
            }
        }
    }
    res=(res+mod)%mod;
    cout<<res<<'\n';
}

signed main() {
	io;
	work();
	return 0;
}

 拖了快两个周断断续续终于补完了这场,感觉期末有点寄,但需要复健一下力。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值