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;
}