A、Two Bracket Sequences
B、K Integers
题意:给定一个1到n的排列,定义移动操作为:交换相邻的两个元素。
现在定义一个函数f(x),表示在原排列中,通过交换操作,形成一个1,2,3…x的排列的子串,需要的最小操作步骤。
现在你需要求出f(1),f(2),f(3)…f(n)。
题解:对于每个k可以看出答案可以分为两个部分,一部分是将1,2,3,…,k移动到相
邻的位置,另一部分是将乱 序的1,2,3,…,k调整为升序。移动的策略是寻找中间位
置,然后计算左右两边移动的代价。将乱序调整为升序的代价为序列中逆序对的
数量。
思路
#include <iostream>
using namespace std;
typedef long long ll;
int n;
const int maxn=2e5+5;
ll a[maxn];
ll pos[maxn];
ll sum1[maxn]; //数的个数
ll sum2[maxn]; //数的下标和
ll lowbit(ll x) {
return x&-x;
}
void add(ll *c, ll x, ll k) {
while (x<=n) {
c[x]+=k;
x+=lowbit(x);
}
}
ll getsum(ll *c, ll x) {
ll ans=0;
while (x>=1) {
ans=ans+c[x];
x-=lowbit(x);
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin>>n;
for (int i=1; i<=n; i++) {
cin>>a[i];
pos[a[i]]=i;
}
ll ans1=0;
for (int i=1; i<=n; i++) {
add(sum1,pos[i],1);
add(sum2,pos[i],pos[i]);
ans1+=i-getsum(sum1,pos[i]); //在pos[i]之前小于等于i的数的数量,逆序对
ll l=1,r=n;
ll mid;
while (l<=r) {
mid=(l+r)>>1;
if (getsum(sum1,mid)*2<=i)
l=mid+1;
else r=mid-1;
}
ll ans2=0;
ll cnt=getsum(sum1,mid);
ll sum=getsum(sum2,mid);
ans2+=mid*cnt-sum-cnt*(cnt-1)/2;
cnt=i-cnt;
sum=getsum(sum2,n)-sum;
ans2+=sum-cnt*(mid+1)-cnt*(cnt-1)/2;
cout<<ans1+ans2<<" ";
}
return 0;
}
C、Domino for Young
题意:给定图形,问用1x2或者2x1的方块最多能填多少个。
题解:黑白染色,块数最少的颜色的块数即为答案。
#include <iostream>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
ll ans=0;
ll l,r;
int x;
l=r=0;
for (int i=1; i<=n; i++){
cin>>x;
ans+=x/2;
if (x%2)
if (i%2) l++;
else r++;
}
ans+=min(l,r);
cout<<ans;
return 0;
}
D、Stone Game, Why are you always there?
题意:一列石子,每次取出给定序列中个数的石子,谁取走最后的谁赢。
题解:求sg函数。对于k个石子,如果取靠边的x个石子,则转移为sg[k-x];如果取中间的石子,则将一个游戏拆成两个游戏考虑。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=1001;
int n,k,a[maxn],sg[maxn];
int tag[maxn];
int mex(int x) {
if (sg[x]>=0) return sg[x];
memset(tag,0,sizeof(tag));
for (int i=0; i<n; i++) {
for (int j=x-a[i]; j>=0; j--){
tag[mex(j)^mex(x-a[i]-j)]=1;
}
}
for (int i=0; ; i++){
if (!tag[i]) {
sg[x]=i;
break;
}
}
return sg[x];
}
int main() {
while (~scanf("%d",&n)) {
memset(sg,-1,sizeof(sg));
for (int i=0; i<n; i++) cin>>a[i];
sort(a,a+n);
n=unique(a,a+n)-a;
int m;
scanf("%d",&m);
sg[0]=0;
while (m--) {
cin>>k;
if (mex(k)) puts("1");
else puts("2");
}
}
return 0;
}
E、Machine Schedule
匈牙利裸题
题意:有两台机器A和B,A机器有n种工作方式,B机器有m种工作方式。共有k个任务。每个任务恰好在一条机器上运行。
如果任务在A机器上运行,就需要转换为模式Xi,如果在B机器上运行,就需要转换为模式Yi。
每台机器上的任务可以按照任意顺序执行,但是每台机器每转换一次模式需要重启一次。
请合理为每个任务安排一台机器并合理安排顺序,使得机器重启次数尽量少。
题解:模式0可以完成的任务不考虑,然后我们考虑建立二分图,机器A和机器B的模式互相连接,某个任务 由A的模式x和B的模式y完成那么x与y相连。 所以题目转换为在这个二分图中找出最少的点使得所有的边都至少有一个端点被选中,即最小点覆盖, 也即二分图最大匹配。
#include <iostream>
#include <cstring>
using namespace std;
int n,m,k;
int line[105][105];
int vis[105];
int r[105];
bool find(int x){
for (int i=1; i<=m; i++){
if (line[x][i] && !vis[i]){
vis[i]=1;
if (r[i]==-1 || find(r[i])){
r[i]=x;
return true;
}
}
}
return false;
}
int main(){
ios::sync_with_stdio(false);
while (cin>>n && n){
cin>>m>>k;
int ans=0;
memset(line,0,sizeof(line));
memset(r,-1,sizeof(r));
int i,x,y;
while (k--){
cin>>i>>x>>y;
line[x][y]=1;
}
for (i=1; i<=n; i++){
memset(vis,0,sizeof(vis));
if (find(i)) ans++;
}
cout<<ans<<endl;
}
return 0;
}