寒假笔记二·CF游记(二)

E. Marbles

原题地址
在这里插入图片描述
在这里插入图片描述
**人话翻译:**给一个序列,定义一次操作为将两个数交换位置,问最少操作几次可以将各个颜色放在一起。

代码:
参考博文
经典TSP(旅行商问题)的变形,状压DP模板题。将单独使两种颜色分开(忽略其他颜色)视为两点间的路径。

#include <cstdio>
#include<iostream>
#include<algorithm>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
using namespace std;
const int N = 3e6+10000;
const int mod = 1e9;
ll dp[N],x,num[20],cnt[20][20];
 
int n,m;
int main() {
  //  freopen("a.txt","r",stdin);
    ios::sync_with_stdio(0);
    ll S = (1<<20)-1;
    cin>>n;
    rep(i, 1, n) {
        cin>>x;
        x--;
        num[x]++;
        rep(j, 0, 19) cnt[x][j] += num[j];
    }
    rep(i, 1, S) dp[i] = 9e18;
    rep(i, 0, S)
        rep(j, 0, 19)
            if((i>>j)&1) {
                ll x = 0;
                rep(k, 0, 19)
                    if(!((i>>k)&1))
                        x += cnt[j][k];
                dp[i] = min(dp[i],dp[i^(1<<j)]+x);
            }
    cout<<dp[S];
    return 0;
}

H. Berland Prospect

原题地址
在这里插入图片描述
在这里插入图片描述
**人话翻译:**在一个有序数列中求最长等差数列

代码:
参考博文
暴力暴力再暴力
唯一的技巧是vector.reserve(x)(预先开x的容器空间)的应用

#include <cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
#define maxn 3003
ll n,a[maxn];
int dp[maxn][maxn],ans=2;
vector<ll>v[maxn];

void check(ll x,ll y)
{
	ll res=a[y]-a[x],ex;
	ex=lower_bound(v[y].begin(),v[y].end(),res)-v[y].begin();//lower_bound查找的是从小到大的数组
	if(ex==v[y].size())
	{
		dp[y][x]=2;
		return;
	}
	if(v[y][ex]==res)
	{
		dp[y][x]=dp[ex+1][y]+1;
		ans=max(ans,dp[y][x]);
	}
	else dp[y][x]=2;
}

int main() 
{
    ios::sync_with_stdio(false);
    ll i,j;
	cin>>n;
	for(i=1;i<=n;i++) cin>>a[i];
	for(i=1;i<=n;i++)
	{
		v[i].reserve(i-1);
		for(j=1;j<i;j++)
		{
			v[i].push_back(a[j]-a[i]);
		}
	}
	for(i=1;i<=n;i++)
		for(j=1;j<i;j++)
			dp[i][j]=2;
	for(i=1;i<=n;i++)
	{
		for(j=1;j<i;j++)
		{
			check(i,j);
		}
	}
	cout<<ans<<endl;
    return 0;
}

C. Two Arrays

原题地址
在这里插入图片描述
在这里插入图片描述
**人话翻译:**在1到n中抽2m个数(可重复)有几种抽法
代码:
时隔半年,差点又没做出来div2的C题。
可看作把n-1个相同的球放到2m+1个不同的盒子
比赛时,在纠结到底是球相同还是盒子相同纠结了半个小时参考博客
然后,大数组合写炸了,后来看~~(剽)~~ 着~~(窃)~~ 网上别人的代码才改成功参考博客
最后终于在比赛结束前7分钟AC了

#include <cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll a,b,x,y,u=1,v=1;
bool vis[22];
vector<int> nPrime(int n) {
	int k = 2;
	vector<int> v;
	while (k <= n) {
		bool isPrime = true;
		int t = sqrt((double)k);
		for (; t > 1; t--) {
			if (k%t == 0) {
				isPrime = false;
				break;
			}
		}
		if (isPrime)
			v.push_back(k);
		k++;
	}
	return v;
}

//计算n!中素数m的次数
int dPrime(int n, int m) {
	int pow = 0;
	while (n >= m) {
		int temp = n / m;
		pow += temp;
		n = temp;
	}
	return pow;
}

//计算组合数C(n,m)
int C(int n, int m) {
	long long ans = 1;
	vector<int> v = nPrime(n);
	for (int i = 0; i < v.size(); i++) {
		int k = v.at(i),pow;
		pow = dPrime(n, k) - dPrime(m, k) - dPrime(n - m, k);
		for (int j = 0; j < pow; j++) {
			ans *= k;
			ans %= (int)(1e9 + 7);
		}
	}
	return (int)ans;
}

int main()
{
	ios::sync_with_stdio(false);
	ll i,j;
	cin>>a>>b;
	x=a+b*2-1;
	y=b*2;
	cout << C(x, y) << endl;
	return 0;
}

然而,有意思的是,第二天剽CF上大佬们的代码,发现它们都长这样:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
#define rep(i,s,e) for(register int i=s;i<=e;i++)
#define repp(i,s,e) for(register int i=s;i<e;i++)
#define dep(i,e,s) for(register int i=e;i>=s;i--)
#define pb push_back
#define pf(a) printf("%d",a)
#define sf(a) scanf("%d",&a)
#define pii pair<int,int>//����ΪP 
#define RI register int
#define mst(a,x) memset(a,x,sizeof(a))
#define db double
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int inf=0x3f3f3f3f;
const int N=1e3+5;
const ll mod=1e9+7;
int n,m;
ll a[N][N];
 
int main(){ 
    rep(i,1,1000){
    	a[i][0]=1;
    	rep(j,1,20){
    		a[i][j]=a[i-1][j]+a[i][j-1];
    		a[i][j]%=mod;
		}
	}
	cin>>n>>m;
	ll p=0;
	cout<<a[n][m*2]<<endl;
	return 0;
}

我一定做了假题。。。
附组合数模板:模板

D. Minimax Problem

原题地址
在这里插入图片描述
懒得翻译了
代码:
接上题C
由于C题交完后比赛只剩7分钟了,所以D只读了个题。显然一开始读是没有思路的,然后第二天到网上搜一搜参考博客,看了眼标题(woc,这题不难啊)
参考博客

#include <cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
#define maxn 300030
int n,m,lim;
int a[maxn][9],qt[maxn],vis[256],u,v;
//qt记录状态,vis[i]代表是i状态的数对应第几个数组
int get(int x,int p)
{
	int i,t=0;
	for(i=1;i<=m;i++)
	{
		t=t<<1;
		if(a[p][i]>=x) t=t|1;
	}
	return t;
}
bool check(int x)
{
	int i,j;
	for(i=0;i<=lim;i++) vis[i]=0;
	for(i=1;i<=n;i++)
	{
		qt[i]=get(x,i);
		vis[qt[i]]=i;
	}
	for(i=0;i<=lim;i++)
	{
		for(j=0;j<=lim;j++)
		{
			if((i|j)==lim&&vis[i]&&vis[j])
			{
				u=vis[i];v=vis[j];
				return true;
			}
		}
	}
	return false;
}
int main()
{
	ios::sync_with_stdio(false);
	int i,j,l,r=0,mid,ans;
	cin>>n>>m;
	lim=(1<<m)-1;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			cin>>a[i][j],r=max(r,a[i][j]);
	l=0;
	while(l+1<r)
	{
		mid=(l+r)>>1;
		if(check(mid)) l=mid;
		else r=mid;
	}
	if(check(r))
	{
		cout<<u<<' '<<v<<endl;
	}
	else
	{
		check(l);
		cout<<u<<' '<<v<<endl;
	}
	return 0;
}

感觉亏了好多rating。。。

CF1290C·Prefix Enlightenment

原题地址

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码:
参考博客
参考了卿学姐的视屏讲解弄明白了这道带权查并集
感觉思路有点像2-SAT,但2-SAT解决的问题和这个不一样
剽来的代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 600005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}
 
int n, k, fa[maxn], size[maxn], l[maxn], r[maxn];
char s[maxn];
int get(int x) {return fa[x] == x? x : fa[x] = get(fa[x]);}
 
void merge(int u, int v) {//带权并查集合并
	u = get(u), v = get(v);
	if(!v) swap(u, v);
	fa[v] = u; 
	if(u) size[u] += size[v];//注意,如果u=0的话不能提供size。若cal函数加个特判倒是可以。
}
 
int cal(int u) {//计算集合u对应的可以提供最少操作次数的选择
	register int v;
	if(u > k) v = u - k; else v = u + k;
	u = get(u), v = get(v);//v是对立集合
	if(!u || !v) return size[u + v];//其中一个不合法,返回另一个
	return min(size[u], size[v]);//否则可以做选择
}
 
signed main() {//1~k是选,k+1~2k是不选
	n = read(), k = read();
	scanf("%s", s + 1);
	for(int i = 1; i <= k + k; i++) fa[i] = i;
	for(int i = 1; i <= k; i++) size[i] = 1;//选了的话权值就是1
	for(int i = 1, x, y; i <= k; i++) {
		x = read(); while(x--) {
			y = read(); if(!l[y]) l[y] = i; else r[y] = i;
		}//l是第一个集合,r是第二个集合
	}
	
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		if(!r[i]) {
			register int u = l[i]; if(u) {//如果压根没有对应集合那肯定直接跳过
				ans -= cal(u);
				if(s[i] == '1') fa[get(u)] = 0;//确定对立面不能选
				else fa[get(u + k)] = 0;
				ans += cal(u);
			}
		} else {
			register int u = l[i], v = r[i];
			if(s[i] == '1') {
				if(get(u) != get(v)) {//确定没有建边过
					ans -= cal(u), ans -= cal(v);
					merge(u, v); merge(u + k, v + k);
					ans += cal(u);//两个集合合并了,一个root,直接cal是可以的
				}
			} else {
				if(get(u) != get(v + k)) {
					ans -= cal(u), ans -= cal(v);
					merge(u, v + k), merge(u + k, v);
					ans += cal(u);
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值