梦熊联盟1月普及组月赛游记

T1

给定 a 1 − n a_{1-n} a1n m m m , 要求 i ∗ p ∗ a i ≥ m i*p *a_i\ge m ipaim i ∗ p ∗ ( a i − 1 ) < m i*p*(a_i-1)<m ip(ai1)<m 求整数 p p p 的可能个数

#include <bits/stdc++.h>
using namespace std;
const double inf = 1e18;
int n, m, cnt, a;
double l, r=inf;
int main()
{
    cin >> n >> m;
    for(int i=1;i<=n;i++)
    {
        cin >> a;
        l = max( l, 1.0*m/(a*i) );
		if(a[i] != 1) 
			r = min( r, 1.0*m/((a-1)*i) );
    }
    if(r==inf) {puts("xiaogougege"); return 0;}
    int cnt = 0;
	for(int i=ceil(l);i<r;i++) cnt++;
    cout << cnt;
}

T2

给定序列 a n a_n an , 现有两个操作 :

  1. 删除 x x x , x x x 没有则输出 − 1 -1 1 跳过
  2. 加入 x x x

对于每个操作 , 输出 ∣ a 1 − a 2 ∣ + ∣ a 2 − a 3 ∣ + . . . ∣ a n − a 1 ∣ |a_1-a_2|+|a_2-a_3|+...|a_n-a_1| a1a2+a2a3+...∣ana1 的最小值

考虑 ∣ a 1 − a 2 ∣ + ∣ a 2 − a 3 ∣ + . . . ∣ a n − a 1 ∣ |a_1-a_2|+|a_2-a_3|+...|a_n-a_1| a1a2+a2a3+...∣ana1 的几何意义 , 那么答案为 2 ∗ ( m a x ( a n ) − m i n ( a n ) ) 2*(max(a_n)-min(a_n)) 2(max(an)min(an))

那么只有影响到最大最小值的操作才会影响答案 , 由于删除操作我们还需要维护次大次小值 , 考虑使用堆

数据较水 , 接近暴力的算法也能过

赛时代码 :

#include <iostream>
using namespace std;
const int MAXN = 1e6+5;
int n,q,mn=1e9,mx=-1,vis[MAXN];
int main()
{
    std::ios::sync_with_stdio(false);std::cin.tie(0);
    cin >> n >> q;
    for(int i=1;i<=n;i++)
    {
        int x; cin >> x; vis[x]++;
        mx = max(mx, x); mn = min(mn, x);
    }
    while(q--)
    {
        int opt,x;
        cin >> opt >> x;
        if(opt == 1)
        {
            if(!vis[x]) cout<<-1<<endl;
            else
            {
                vis[x]--;
                if(x == mx)
                    for(int i=mx;i>=mn;i--)
                        if(vis[i]) {mx = i; break;}
                if(x == mn)
                    for(int i=mn;i<=mx;i++)
                        if(vis[i]) {mn = i; break;}
                cout << 2*(mx-mn) << endl;
            }
        }
        if(opt==2)
        {
            vis[x]++;
            mn = min(mn, x); mx = max(mx, x);
            cout << 2*(mx-mn) << endl;
        }
    }
}

正解 (堆) :

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+5;
priority_queue <int> q1,q2; 
int n, q, vis[MAXN];
int main()
{
	cin >> n >> q;
	for(int i=1;i<=n;i++)
	{
		int x; cin >> x; vis[x] ++ ;
		q1.push(x); q2.push(-x);
	}
	while(q--)
	{
		int opt, x;
		cin >> opt >> x;
        if(opt == 1)
        {
            if(!vis[x]) cout<<-1<<endl;
            else
            {
                vis[x]--;
                while(!vis[q1.top()]) q1.pop();
                while(!vis[-q2.top()]) q2.pop();
                cout << 2*(q1.top()+q2.top()) << endl;
            }
        }
        if(opt==2)
        {
            vis[x]++;
            q1.push(x); q2.push(-x);
            cout << 2*(q1.top()+q2.top()) << endl;
        }
	}
	return 0;
}


T3

给定 a n a_n an 你可以花费 r − l + 1 + k r-l+1+k rl+1+k 将区间 [ l , r ] [l,r] [l,r] 变为 g c d ( a l , a l + 1 . . . . , a r ) gcd(a_l,a_{l+1}....,a_r) gcd(al,al+1....,ar) , 求将序列变相同的最小花费

容易想出最终的答案数列一定都为 G = g c d ( a 1 , a 2 . . . . a n ) G=gcd(a_1,a_2....a_n) G=gcd(a1,a2....an)

我们令 f i f_i fi 表示将 a 1 − a i a_1-a_i a1ai 变成 G G G 的最小代价 , 那么有 f i = m i n { f j + i − j + k } ( 0 ≤ j < i ) f_i=min\lbrace f_j+i-j+k \rbrace(0\le j< i) fi=min{fj+ij+k}(0j<i) ( 将 [ j + 1 , i ] [j+1,i] [j+1,i] 操作变为 G G G )

可以写出一个 O ( n 3 ) O(n^3) O(n3) 的朴素dp , 可以拿到 40 p t s 40pts 40pts

#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e6+5;
const int inf = 1e9;
int n, k, a[MAXN], f[MAXN];
int calc(int l,int r)
{
	int res = a[l];
	for(int i=l+1;i<=r;i++) res = __gcd(res, a[i]);
	return res;
}
int main()
{
	cin >> n >> k;
	for(int i=1;i<=n;i++)  cin >> a[i];
 	int G = calc(1,n);
	for(int i=1;i<=n;i++)  f[i] = inf;
	for(int i=1;i<=n;i++)
	{
		if(a[i] == G) f[i] = f[i-1];
		for(int j=0;j<i;j++)
		{
			if(calc(j+1,i)==G)
				f[i] = min(f[i], f[j]+i-j+k);	
		}
	} 
	cout << f[n] <<endl;
	return 0;
}

n ≤ 1000 n\le 1000 n1000 时 , 我们可以 O ( n 2 ) O(n^2) O(n2) 预处理出 g c d ( a i . . . . a j ) gcd(a_i....a_j) gcd(ai....aj) , 可以拿到 60 p t s 60pts 60pts

for(int i=1;i<=n;i++)
{
	g[i][i] = a[i];
	for(int j=i+1;j<=n;j++)
	{
		g[i][j] = __gcd(g[i][j-1], a[j]);
	}
}
正解 :

每个数至多变一次 . 我们将数列划分成若干个连续为 G G G 的区间以及若干个其他区间 .

. . . . . . G G . . . . . . . . G G G G . . . . . . . . . . G G G . . . . . ......GG. .......GGGG..........GGG..... ......GG........GGGG..........GGG.....
[ 1 ] [   2   ] [ 3 ] [   4   ] [\enspace1\enspace]\quad[\enspace\space2\enspace\space]\qquad\quad[\quad3\quad]\quad\quad[\space4\space] [1][ 2 ][3][ 4 ]

f i f_i fi 为前 i i i其他区间变为 G G G 的最小花费

对于第 i i i 个区间 , 可选择将其变为连续 G G G 区间 , 代价为 r i − l i + 1 + k + ( g c d ( a l i . . . a r i )   ! = G ) r_i-l_i+1+k+(gcd(a_{l_i}...a_{r_i})\space!=G) rili+1+k+(gcd(ali...ari) !=G) ( 是否需要借助前面的一个 G G G )

也可选择将第 j + 1 j+1 j+1 i i i 个区间合并 ( 0 ≤ j < i − 1 ) (0\le j<i-1) (0j<i1) 合并 , j + 1 , i j+1,i j+1,i 间必定跨过一段连续 G G G 的区间 , 因此代价即为 r i − l j + 1 + 1 + k r_i-l_{j+1}+1+k rilj+1+1+k

f i = m i n { f i − 1 + r i − l i + 1 + k + ( g c d ( a l i . . . a r i )   ! = G ) f j + r i − l j + 1 + 1 + k ( 0 ≤ j < i − 1 ) f_i=min\begin{cases} \small f_{i-1}+r_i-l_i+1+k+(gcd(a_{l_i}...a_{r_i})\space!=G)\\ f_j+r_i-l_{j+1}+1+k(0\le j<i-1) \end{cases} fi=min{fi1+rili+1+k+(gcd(ali...ari) !=G)fj+rilj+1+1+k(0j<i1)

这样做仍然是 O ( n 2 ) O(n^2) O(n2) 的 , m i n ( f j + r i − l j + 1 + 1 + k ) = m i n ( f j − l j + 1 ) + r i + k + 1 min(f_j+r_i-l_{j+1}+1+k)=min(f_j-l_{j+1})+r_i+k+1 min(fj+rilj+1+1+k)=min(fjlj+1)+ri+k+1 动态维护 m i n ( f j − l j + 1 ) min(f_j-l_{j+1}) min(fjlj+1) 即可

AC 代码

#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e6+5;
const int inf = 1e9;
int n, k, a[MAXN], f[MAXN], l[MAXN], r[MAXN], tot=1;
int calc(int l,int r)
{
	int res = a[l];
	for(int i=l+1;i<=r;i++) res = __gcd(res, a[i]);
	return res;
}
int main()
{
	std::ios::sync_with_stdio(false);std::cin.tie(0);
	cin >> n >> k;
	for(int i=1;i<=n;i++) cin >> a[i];
	int G = calc(1,n);
	for(int i=1;i<=n;i++)
	{
		if(a[i]!=G && l[tot]==0) l[tot] = i;
		if(a[i]==G && l[tot]) r[tot++] = i-1;
	}
	if(l[tot]) r[tot]=n; else tot--;
	int mn = inf;
	for(int i=1;i<=tot;i++)
	{
		f[i] = f[i-1]+r[i]-l[i]+1+k+(calc(l[i],r[i])!=G);
		f[i] = min(f[i], mn+r[i]+k+1);
		mn = min(mn, f[i-1]-l[i]);
	} 
	cout << f[tot] <<endl;
	return 0;
}

T4

给定 n , k , p n,k,p n,k,p 要求序列 a 1 − p a_{1-p} a1p 满足 :

  1. 1 ≤ a i ≤ n 1\le a_i \le n 1ain
  2. a i ∧ a i + 1 a_i \land a_{i+1} aiai+1 二进制 1 1 1 的个数为 k k k
  3. a p a_p ap 中两两互不相同

考虑到限制较多 , 经过一些预处理 , 暴力的复杂度并不会特别高 , 可以拿到 72 72 72

赛时代码:

#include <iostream>
#include <vector>
using namespace std;
const int mod = 998244353;
int n, k, p, vis[1005];
vector <int> G[1005];
int dfs(int i, int lst)
{
    if(i==p+1) return 1;
    int res = 0 ;
    for(auto d : G[lst])
    {
        if(vis[d]) continue;
        vis[d] = 1;
        res += dfs(i+1,d), res%=mod;
        vis[d] = 0;
    }
    return res;
}
int main()
{
    cin >> n >> k >> p;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(__builtin_popcount(i^j)==k)
                G[i].push_back(j);
    if(p==1) {cout<<n<<endl;return 0;}
    int res = 0;
    for(int i=1;i<=n;i++)
    {
        vis[i] = 1;
        res += dfs(2,i), res%=mod;
        vis[i] = 0;
    }
    cout << res;
}
正解 :

c n t [ i ] cnt[i] cnt[i] 表示满足 p o p c o u n t ( i ∧ x ) = k popcount(i\land x)=k popcount(ix)=k x x x 个数

p = 1 p=1 p=1

if(p==1) {cout<<n<<endl;return 0;}

p = 2 p=2 p=2

枚举第一位为 i i i , 第二位有 c n t [ i ] cnt[i] cnt[i] 种填法

if(p==2) 
{
	int res= 0; 
	for(int i=1;i<=n;i++)	
		res+= G[i].size();
	cout << res<< endl;
}

p = 3 p=3 p=3

枚举第二位为 i i i , 第一位可以填 c n t [ i ] cnt[i] cnt[i] 种 , 由于不重复 , 最后一位可以填 c n t [ i ] − 1 cnt[i]-1 cnt[i]1

if(p==3) 
{
	int ans = 0; 
	for(int i=1;i<=n;i++)	
		ans += G[i].size() * (G[i].size()-1);
	cout << ans << endl;
}

p = 4 p=4 p=4

枚举中间两位 : 则其余两位应该有 ( c n t [ i ] − 1 ) ∗ ( c n t [ j ] − 1 ) (cnt[i]-1)*(cnt[j]-1) (cnt[i]1)(cnt[j]1) (不与 i , j i,j i,j 重复) , 但需要减去左右两位重复的情况.

可以使用 bitset统计重复个数 :

(B[i]&B[j]).count()表示满足 p o p c o u n t ( i ∧ x ) = k popcount(i\land x)=k popcount(ix)=k p o p c o u n t ( j ∧ x ) = k popcount(j\land x)=k popcount(jx)=k x x x 的个数

if(p==4)
{
	for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(__builtin_popcount(i^j)==k)
				B[i].set(j), B[j].set(i);
			
	int ans = 0;
	for(int i=1;i<=n;i++)
	{
		for(auto j : G[i])
		{
			ans += (G[i].size()-1) * (G[j].size()-1) - (B[i]&B[j]).count(), ans%=mod
		}
	}
	cout << ans << endl;
}

p = 5 p=5 p=5

枚举 2 , 4 2,4 2,4 位为 i , j i,j i,j , 则第 3 3 3 位有(B[i]&B[j]).count()种填法 , 则若满足 p o p c o u n t ( i ∧ x ) = k popcount(i\land x)=k popcount(ix)=k x x x 中包含 j j j , 那么第 1 1 1 位应该为 c n t [ i ] − 2 cnt[i]-2 cnt[i]2 种 , 否则有 c n t [ i ] − 1 cnt[i]-1 cnt[i]1 种 , 用 bitset表示即为 c n t [ i ] − 1 − B [ i ] [ j ] cnt[i]-1-B[i][j] cnt[i]1B[i][j] , 同理第 5 5 5 位有 c n t [ j ] − 1 − B [ j ] [ i ] cnt[j]-1-B[j][i] cnt[j]1B[j][i] 种 . 最终减去 1 , 5 1,5 1,5 重复的情况 , 因为 已经排除 1 , 5 1,5 1,5 3 3 3 重复的情况 , 因此 − 1 -1 1 即为: (B[i]&B[j]).count()-1

if(p==5)
{
	int ans = 0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i==j) continue;
			int x = (B[i]&B[j]).count();
			ans += x*((G[i].size()-1-B[i][j])*(G[j].size()-1-B[j][i])-(x-1)), ans%=mod;
		}
	}
	cout << ans << endl;
}

AC 代码:

#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int n, k, p, ans;
vector <int> G[1005];
bitset <1005> B[1005];
int main()
{
    cin >> n >> k >> p;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(__builtin_popcount(i^j)==k)
				G[i].push_back(j),B[i].set(j), B[j].set(i);
			
    if(p==1) {cout<<n<<endl;}
    if(p==2) 
	{
		for(int i=1;i<=n;i++) ans += G[i].size();
		cout << ans << endl;
	}
	if(p==3) 
	{
		for(int i=1;i<=n;i++)	
			ans += G[i].size() * (G[i].size()-1);
		cout << ans << endl;
	}
	if(p==4)
	{
		for(int i=1;i<=n;i++)
			for(auto j : G[i])
				ans += (G[i].size()-1) * (G[j].size()-1) - (B[i]&B[j]).count(), ans%=mod;
		cout << ans << endl;
	}
	if(p==5)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(i==j) continue;
				int x = (B[i]&B[j]).count();
				ans += x*((G[i].size()-1-B[i][j])*(G[j].size()-1-B[j][i])-(x-1)), ans%=mod;
			}
		}
		cout << ans << endl;
	}
}

总结 : 这场比赛拿到 100 + 100 + 60 + 72 100+100+60+72 100+100+60+72 还是不难的 , 但本人考场经验太少 , 经常犯蠢 , 所以多打月赛 / 模拟赛

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值