cf 之lis+贪心+思维+并查集

https://codeforces.com/contest/1257/problem/E

题意:有三个集合集合里面的数字可以随意变换位置,不同集合的数字,如从第一个A集合取一个数字到B集合那操作数+1,求最少操作次数使得一二三集合连起来是升序

我们先把每个集合排个序,然后拼起来,求一遍lis(最长升序序列:https://blog.csdn.net/Joined/article/details/78058362),然后n-len即为答案

网上那些什么dp什么的。。嘻嘻太高深

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+7;
int k1,k2,k3,n,a[maxn],dp[maxn],b[maxn],len;
void lis(){
    len=1;b[1]=a[0];
    for(int i=1;i<n;i++){
        b[len+1]=n+1;
        int l=0,r=len+1,ans=0;
        while(l<=r){
            int mid=(l+r)/2;
            if(a[i]<b[mid]){
                ans=mid;
                r=mid-1;
            }else{
                l=mid+1;
            }
        }
        b[ans]=a[i];
        if(ans>len)len++;
    }
    cout<<n-len<<endl;
}
int main(){
    scanf("%d%d%d",&k1,&k2,&k3);
    n=k1+k2+k3;
    for(int i=0;i<k1+k2+k3;i++){
        scanf("%d",&a[i]);
    }
    sort(a,a+k1);
    sort(a+k1,a+k1+k2);
    sort(a+k1+k2,a+k1+k2+k3);
    lis();
}

  https://codeforces.com/contest/1257/problem/D

赛后才想出来的一道题:

题意大概是,给你一串怪物,怪物有攻击力,你有几个英雄,每个英雄有攻击力和耐力,攻击力大于怪物的你就击败一个怪物,但耐力减少一,改天结束的条件是,改英雄耐力耗完或者是改英雄攻击力打不过怪物无奈退出,每天可安排一个英雄,英雄可多次使用,至少多少天清楚掉这些怪物。

很容易想到二分找到一个大于该怪物的的英雄,贪心选取最优的一个。

思路:我们先排序

bool cmp(node A,node B)
{
if (A.pi!=B.pi) return A.pi<B.pi;
return A.si>B.si;
}

排完序后,我们要对这些英雄进行优化,为什么要优化呢?因为我们的思路为以下

遇到第一个怪物然后二分查找第一个英雄,然后枚举怪物,英雄遇到一个打不过的,我们往后找,英雄战力是递增的嘛,而且我们希望找到的是英雄更大耐力也更大的吧,你肯定不想找耐力反而更少的,浪费时间。

所以我们的优化有两个,第一出现相同战力的英雄经过我们上面的排序,剔除掉耐力低的。

第二,由于我们的战力从小排到大,出现战力大于a,耐力又大于a的b,肯定是直接舍弃a,经过这些筛选,就减少了不必要的枚举。

接下来就可以开始枚举了

#include <bits/stdc++.h>
#define N 200010
using namespace std;
int res,len,now,now2,tmp,maxx;
int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
struct node{
	int pi,si;
}b[N];
int n,tmpp;
int a[N];
bool cmp(node A,node B)
{
	if (A.pi!=B.pi) return A.pi<B.pi;
	return A.si>B.si;
}
inline erfen(int x)
{
	int l=1,r=tmpp,mid,t;
	while (l<=r){
		mid=(l+r)/2;
		if (x==b[mid].pi) return mid;
		if (x<b[mid].pi) r=mid-1,t=mid;
		else l=mid+1;
	}
	return t;
}
int main()
{
//	freopen("t.txt","r",stdin);
    int m;
	int T=read();
	while (T--)
	{
		n=read();
		maxx=0;
		for(int i=1;i<=n;i++)
		a[i]=read(),maxx=max(maxx,a[i]);
		m=read();
		for (int i=1;i<=m;i++)
		b[i].pi=read(),b[i].si=read();
		sort(b+1,b+m+1,cmp);
		if(maxx>b[m].pi){
			puts("-1");
			continue;
		}
		tmp=1;
		for(int i=2;i<=m;i++)
            if(b[i].pi!=b[tmp].pi) b[++tmp]=b[i];
		tmpp=tmp;
		tmp=1;
		for(int i=2;i<=tmpp;i++)
		{
			while (b[i].si>=b[tmp].si&&tmp>0) tmp--;
			b[++tmp]=b[i];
		}
		tmpp=tmp;
		res=0;
		now=1;
		while(now<=n)
		{
			len=1;
			now2=erfen(a[now]);
			while(b[now2].si>len){
				if(a[now+1]<=b[now2].pi) now++,len++;
				else{
					while(a[now+1]>b[now2].pi&&len+1<=b[now2].si) now2++;
					if(a[now+1]>b[now2].pi||len+1>b[now2].si) break;
					now++;len++;
				}
			}
			res++;
			now++;
		}
		printf("%d\n",res);
	}
}

  

 Ivan the Fool and the Probability Theory

给定一个N×M N\times MN×M的方格图,现在要求将每个格子染成黑白两种颜色,要求每个格子至多和四个方向(上下左右)上的一个格子同色,问有多少种方案。
我们先以一维的时候来解释,一个格子的时候有两种情况,二个格子有四个情况,那么三个格子的时候就是要么第一格+两个相同颜色的,要么就是第二个格子加上一个格子,所以

就是f1+f2斐波那契就出来了

接下来思考二维,实际上例如第一行任意一个地方连续出现两个方块(称之为行连续),那么第二行必须与上面状态相反,以此类推,第一行确定了,下面的颜色随之确定了,(那还有第一行不出现连续的呢? 那就是间隔的,那无非就是黑白黑白 然后白黑白黑两种,他下一行必定和他相反,这就和上面同个道理(。。。相同不行?  嗯肯定是可以的,但是相同了,不就出现相邻有相同颜色了嘛,(我们自己称之为列连续)那不就和上面的行连续一个道理吗,所以第一列状态确定,后面的也随之确定

那不就好起来了,f【n】+f【m】-2,-2是因为行的时候我们计算了黑白黑白 白黑白黑,列的时候依然会计算到这个,重复了两个。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1E5+7;
ll dp[N];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    int x=max(n,m);
    dp[1]=2;
    dp[2]=4;
    for(int i=3;i<=x;i++) dp[i]=(dp[i-1]+dp[i-2])%mod;
    ll sum=(dp[n]+dp[m]-2+mod)%mod;
    cout<<sum<<endl;
    return 0;
}

 https://codeforces.com/contest/1253/problem/D

看代码很容易理解,并查集

#include <bits/stdc++.h>
 
using namespace std;
 
set<int> s[200010];
 
int n,m,x,y;
int p[200010];
int mx[200010];
int mn[200010];
long long sum[200010];
 
void init() {
    for (int i = 0; i < 200010; ++i) {
        p[i] = i;
        mn[i] = 1e9;
    }
}
 
int find (int x) {
    return x == p[x] ? p[x] : p[x] = find(p[x]);
}
void merge(int x, int y) {
    p[find(y)] = find(x);
}
int main() {
    init();
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; ++i) {
        scanf("%d %d", &x, &y);
        if(find(x) != find(y))
            merge(x,y);
    }
    set<int> parents;
    for (int i = 1; i <= n; ++i) {
        parents.insert(find(i));
        mx[find(i)] = max(mx[find(i)], i);
        mn[find(i)] = min(mn[find(i)], i);
    }
    vector<pair<int, int> > r;
    for(set<int>::iterator it=parents.begin();it!=parents.end();it++)
        r.push_back({mn[*it], mx[*it]});
 
    sort(r.begin(), r.end());
    priority_queue<int > q;
    int ans = 0;
    for(int i = 0; i < (int) r.size(); ++i) {
        while(!q.empty() and q.top()< r[i].first) q.pop();
        if(!q.empty()) ans++;
        q.push(r[i].second);
    }
 
    cout << ans << "\n";
    return 0;
}

  

D1/D2. The World Is Just a Programming Task
题目大意
有一个括号序列,你需要选择两个位置,将位置上的括号交换,使得最后得到的循环位置最大。

循环位置的定义是:我们将前i ii个位置上的字符串取出放到字符串的最后,所得的括号序列是一个合法的序列。

分析
首先我们可以知道,对于一个左右括号数量并不相同的序列,它没有循环位置。我们将这种情况直接特判掉。

我们先将整个括号序列转成一个合法的序列。容易证明这是一定存在的。

那么这个序列的循环节位置就长这样,就是一级括号所在的位置:


那么我们可以发现,对于若我们交换一个一级括号的左右括号,如下:

若我们记翻转的一级括号内的二级括号数量为a aa,那么这个操作的答案就是a+1 a+1a+1。

我们也可以交换一个二级括号的左右括号。

简单画图可以知道答案是一级括号的数量+翻转的二级括号内的数量+1。

通过画图可以发现翻转一个三级括号是没用的。

O(N) O(N)O(N)扫一遍就行了。

#include <cstdio>
#include <algorithm>
using namespace std;

const int Maxn = 300000;

int N, s[Maxn * 2 + 5];
char str[Maxn + 5];
int lef[Maxn * 2 + 5], dep[Maxn * 2 + 5];
int siz[Maxn * 2 + 5], sum[Maxn * 2 + 5];
int stk[Maxn + 5], tp;

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d %s", &N, str + 1);
	int cnt = 0;
	for(int i = 1; i <= N; i++) {
		s[i] = s[i + N] = (str[i] == '(' ? 1 : -1);
		cnt += s[i];
	}
	if(cnt) {
		puts("0\n1 1");
		return 0;
	}
	int pos = 1;
	for(int i = 1; i <= N * 2; i++) {
		cnt += s[i];
		if(cnt < 0) pos = i + 1, cnt = 0;
		else if(i - pos + 1 >= N) break;
	}
	for(int i = pos; i <= pos + N - 1; i++)
		if(s[i] == -1) {
			int p = stk[tp--];
			sum[tp]++, lef[i] = p, dep[i] = tp;
			siz[i] = sum[tp + 1], sum[tp + 1] = 0;
		} else stk[++tp] = i;
	int ans = sum[0], l = 1, r = 1;
	for(int i = pos; i <= pos + N - 1; i++) {
		if(dep[i] == 0)
			if(siz[i] + 1 > ans)
				ans = siz[i] + 1, l = lef[i], r = i;
		if(dep[i] == 1)
			if(sum[0] + siz[i] + 1 > ans)
				ans = sum[0] + siz[i] + 1, l = lef[i], r = i;
	}
	printf("%d\n%d %d\n", ans, (l - 1) % N + 1, (r - 1) % N + 1);
	return 0;
}

 https://codeforces.com/contest/1253/problem/C

给个序列,给个m,每一天最多选m个,第k天地贡献是所选地m个元素和乘以k。

问1到n天,每天的方案,不可选重复,最小地贡献是多少,如果简单地贪心暴力算,就会超时,

在纸上画一下过程,当大于m天时,就是讲后来的序列放到第一天,第一天有一个到第二天,第二天有一个到第三天,所以每个数字是有以m为周期的贡献的。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx=200005;
ll a[maxx],b[maxx];
int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int main()
{
// freopen("t.txt","r",stdin);
 
    int n=read(),m=read();
    ll ans=0;
    for(int i=1;i<=n;i++) a[i]=read();
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++){
        b[i]=a[i];
        ans+=a[i];
        if(i-m>0) b[i]+=b[i-m],ans+=b[i-m];
        if(i==n) printf("%lld\n",ans);
        else printf("%lld ",ans);
    }
    return 0;
}

https://codeforces.com/contest/1253/problem/B

简单说一个整数k表示k进去,而-k表示他出来,每个整数一天只可能进去一次,所以出现1 -1 1 -1,说明这不是同一天的,同样,当整个办公室没人了,说明这一天已经过了。

所以模拟一下过程

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef double ld;
typedef vector<ll> vi;
#define F(a) for ( ll i = 0; i < (ll)(a); ++i )
ll read(){
    char c=getchar();ll x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
   return x*f;
}
int main() {
// freopen("t.txt","r",stdin);
    ll n;n=read();
    vi a(n);for(int i=0;i<n;i++)a[i]=read();
    map<ll,ll> m;
    set<ll> visit;
    vi res;
    ll len=0,cnt=0;
    bool ok=1;
    for(int i=0;i<n;i++){
        ll c=a[i];
        ++len;
        if(c>0){ // enter
            if(visit.count(c)) ok=0;
            if(m[c]++ == 0){
                visit.insert(c);
                cnt++;
            }
        }else{ // leave
            c=-c;
            --m[c];
            if(m[c] == 0){
                if(--cnt == 0){
                    visit.clear();
                    res.push_back(len);
                    len=0;
                }
            }else if(m[c]<0){
                ok=0;
            }
        }
    }
    if(cnt)ok=0;
    if(ok){
        cout<<res.size()<<endl;
        for(int i=0;i<res.size();i++)cout<<res[i]<<' ';
        cout<<endl;
    }else{
        cout<<-1<<endl;
    }

 https://codeforces.com/contest/1239/problem/C

题意:每个人按座位号从左到右做好,每个人有一个t值,到达t时刻就会开始渴,每个人喝水的时间是p,然后就是一个人渴了如果他前面的座位有人已经在排队或者正在接水,那么他就不会去排队,反之则排。事实上就是模拟

搞一个优先队列维护一下事件结构体:时间,人的编号,入队还是出队

再维护两个 setset ,队列内的人 inQueueinQueue ,想要进入队列内的人 wantwant

然后模拟模拟模拟!

初始把所有入队事件塞到优先队列,顺便维护一下当前最后一个取完水的时刻

每次取出优先队列里面时间最小的,时间相同优先取入队的,同时间都入队优先取编号小的

然后如果是入队,那么看看当前队列内编号最小的比较一下编号,然后根据比较结果看看是直接入队还是先塞到 wantwant 里面

如果是出队,直接更新一下答案和队列,然后看看 wantwant 里面有没有人能入队

然后就做完了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e5+7;
int n,P;
ll ans[N];
struct dat {
    ll tim; int id; bool flag;
    dat (ll _tim=0,int _id=0,bool _flag=0) { tim=_tim,id=_id,flag=_flag; }
    inline bool operator < (const dat &tmp) const {
        if(tim!=tmp.tim) return tim>tmp.tim;
        return flag!=tmp.flag ? flag>tmp.flag : id>tmp.id;
    }
}D[N];
priority_queue <dat> Q;
set <int> inQ,wnt;
int main()
{
    n=read(),P=read();
    for(int i=1;i<=n;i++) D[i]=dat(read(),i,0);
    for(int i=1;i<=n;i++) Q.push(D[i]);
    ll las=0;
    while(!Q.empty())
    {
        dat t=Q.top(); Q.pop();
        if(!t.flag)//要入队地的
        {
            if(inQ.empty()||*inQ.begin()>t.id)//对物说空或最小的座位号都大于这个要入对的,则可以进来
            {
                las=max(las,t.tim);//不要多想就是取最大的,因为上一次在喝水的时候你是不能排队的
                Q.push(dat(las+P,t.id,1));//然后把要出队的时间弄好,加个出队标志“1”,然后放到优先队列哩,按所说的规则进行排序
                
                inQ.insert(t.id); //队伍更新一人
                las+=P;
            }
            else wnt.insert(t.id);//如果是无法排队,放进想进队伍的set里
            continue;
        }
        ans[t.id]=t.tim; inQ.erase(t.id);//此时是出队的更新这个出队ide,队列里删除这个人
        if(!wnt.empty() && (inQ.empty()||*inQ.begin()>*wnt.begin()))//出队的更新完,然后我们就有空来看看想进队里的
        {
            auto p=wnt.begin(); inQ.insert(*p);
            Q.push(dat(las+P,*p,1)); las+=P; wnt.erase(p);
        }
    }
    for(int i=1;i<=n;i++) printf("%lld ",ans[i]); puts("");
    return 0;
}

  

原文地址:https://www.cnblogs.com/hgangang/p/11871685.html