11.1纪中DAY4_分队问题 & 数字对 & 高级打字机

noip2019…counting down three weeks

纪中day4
(头发日益益益益稀少)

11.1纪中B组notes

  • 分队问题(洛谷 p2062)
  • 数字对(洛谷 p2090)
  • 高级打字机(洛谷 p1383)
仍旧是蒟蒻的悲惨一天o(╥﹏╥)o

keys & notes 部分来源于纪中题解

T1 分队问题(洛谷 p2062)

CEOI 2011 team简化…

题目描述

jzoj 3792 分队问题
洛谷 2062 分队问题

给定n个选手,将他们分成若干只队伍。
其中第i个选手要求自己所属的队伍的人数大等于a[i]人。
在满足所有选手的要求的前提下,最大化队伍的总数。
注:每个选手属于且仅属于一支队伍。

Input
第一行一个整数n,表示人数。
以下n行,每行一个整数表示a[i]。

Output
输出队伍总数的最大值。数据保证有解。

Sample Input
5
2
1
2
2
3

Sample Output
2

Data Constraint
对于20%的数据,n <= 10
对于40%的数据,n <= 1000
对于60%的数据,n <= 10000
对于100%的数据,1 <= n <= 10^6

notes&keys

DP

令f [ i ] 表示将1~i 个人分队的最多分队数。

f [ i ] = max { f [ j ] } + 1 , j <= i - a[i]
那么只需要g [ i ] = max { f [ j ] }, j <= i 即可实现O(n)

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000015;
int n, a[maxn], f[maxn], g[maxn];  

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 * 10 + ch - '0';
		ch = getchar();
	}
	
    return x * f;
}


int main()
{
	n = read();
	for(int i = 1; i <= n; ++i) 
		a[i] = read();                         
	sort(a + 1, a + n + 1);
	for(int i = 1; i <= n; ++i)
	{
		f[i] = g[i - a[i]] + 1;	
		//预留出后边儿a[i]个,后边儿最多成一组,前面的 i - a[i]个数的最大分队数+1 
		g[i] = max(g[i - 1], f[i]);	
		//预处理前面的(类似最大前缀和)分队数,求出转移最大值 
	}
	printf("%d", f[n]);	//不知为啥输出f?? 

	return 0;
}

↑ 别看上边儿的注释都是水…

jz题解...
jz题解...

··另一十分神奇的写法+特判…
在洛谷AC了的代码…

#include<bits/stdc++.h>
using namespace std;
#define INF 1<<30
#define maxn 1000010

int read()
{
    char ch = getchar();
	int ret = 0, flag = 1;
    while(ch < '0'|| ch > '9') 
	{
		if(ch == '-') 
			flag = -1; 
		ch = getchar();
		}
    while(ch >= '0' && ch <= '9') 
	{
		ret = ret * 10 + ch - '0';
		ch = getchar();	
	}
    return ret * flag;
}

int a[maxn],n,f[maxn];
int main()
{
	n = read();
	memset(a,0,sizeof(a));
	memset(f,0,sizeof(f));
	for(int i = 1; i <= n; i ++)
		a[i] = read();
	sort(a + 1,a + n + 1);
	for(int i = 1; i <= n; i ++)
	{
		if(i - a[i] >= 0)
			f[i] = max(f[i - 1],f[i - a[i]]+ 1); 
	}
	if(n - a[n] == 0)//特判!!
	{
		f[n] = 1;
	}	
	printf("%d\n",f[n]);
	return 0;
}

附:艰难爆肝一上午的艰难秃头过程…
也可直接:

f[i] = max ( f[ i - 1], f[i - a[ i] ] + 1)
	//f[ i - 1]第i个人不开新队,最大分队数不变 
	//f[i - a[ i] ] + 1第i个人开新队,最少保证后边儿还有a[i]个人
sum = max(sum, f[i])

↑错的!!!

改为:

g[n] = f[i - a[ i] ] + 1
f[i] = max ( f[ i - 1], g[i])
输出f[i]

**↑还是错的!! **

f[n]相当于标程中的g(预处理效果??),最后应输出g[n]!!

再改:(对于加了一位反而所有加在一起都只能分一组的特判(?)

(循环内)

if(i - a[i] >= 0)
{

 	g[n] = f[i - a[ i] ] + 1
	f[i] = max ( f[ i - 1], g[i])
}
(循环外) 

if(n - a[n] == 0)
	f[n] = 1;
输出f[n] 

T2 数字对(洛谷 p2090)

题目出处
CodeForces 134 B

题目描述

jzoj 3793 数字对
洛谷 p2090 数字对

Description
对于一个数字对(a, b),我们可以通过一次操作将其变为新数字对**(a+b, b)或(a, a+b)。**
给定一正整数n,问最少需要多少次操作可将数字对(1, 1)变为一个数字对,该数字对至少有一个数字为n

Input
第一行一个正整数 n

Output
一个整数表示答案。

Sample Input
5

Sample Output
3
【样例解释】
(1,1)  →  (1,2)  →  (3,2)  →  (5,2)

Data Constraint
对于30%的数据, 1 <= n <= 1000
对于60%的数据, 1 <= n <= 20000
对于100%的数据,1 <= n <= 10^6

notes&keys

来自某不知名巨佬的讲解…

反向考虑:(n,i)如何变为(1,1)?

↓ i 一定小于等于(?)n
↓每次变为(n - i,i)×—复杂度会太高!

→类似辗转相除
gcd(i, n % i ) + n / i ;

枚举1~(n + 1)/ 2(余下状态都是可以对应转移过来的)

枚举
改版gcd跑,找操作次数最少

附上蒟蒻的AC代码

#include <bits/stdc++.h>
#define inf int(1e8)
using namespace std;

int n,now=inf;

int gcd(int a, int b) 
{
	while (b != 0)
	{
		if(b == 1)
			return a - 1;//b最终=1说明一直没动,光给a加值去了 
		else
		{
			return gcd(b, a % b) + a / b;//like辗转相除 
		} 
		
	}
	return inf;	//b=0的话说明不存在,返回无限大 
}



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 * 10 + ch - '0';
		ch = getchar();
	}
	
    return x * f;
}

int main()
{
	n = read();
	for(int i = 1; i <= (n + 1) / 2; i++)
		now = min(now, gcd(n, i));
    printf("%d\n", now);

	return 0;
}

带注释版代码(from巨佬blt~)

#include<bits/stdc++.h>
using namespace std;
#define INF 1<<30

int read()
{
    char ch = getchar();
	int ret = 0, flag = 1;
    while(ch < '0'|| ch > '9') 
	{
		if(ch == '-') 
			flag = -1; 
		ch = getchar();
		}
    while(ch >= '0' && ch <= '9') 
	{
		ret = ret * 10 + ch - '0';
		ch = getchar();	
	}
    return ret * flag;
}
int magic(int a, int b)
{
	if(!b)//要是b等于零,b显然不可能为零嘛
	//最小都是(1,1),之后又一直加的,怎么会有零 
	//要是说是后面取模取到整除的话,那也是不可能的,手动模拟一下就知道了
	//e.g.:最简单,(2,2),一看就知道,根本是行不通的嘛 
		return INF;
	if(b == 1)
	//要是b等于1,那就一直加另一边啊,因为另一边本来就有1,所以操作数要减1 
		return a - 1;
	return magic(b, a % b) + a / b;
	//这个其实挺好理解的,就是 n * b + a % b = a 
	//e.g.:(8,3) = (3,2) + 2   也就是说,把 3 往 2 那里加两次,就可以得到(8,3)了
}
int n;
int now = INF;
int main()
{
	n = read();
	for(int i = 1; i <= (n + 1) / 2; i ++)
	//只用到一半就好啦,因为前一半和后一半的结果是可以由同一个状态转移过去的
	{
		now = min(now, magic(n,i));
		//然后枚举,看看最后一步要从哪转移才能让之前的步数最小,并记录最小步数
	}
	printf("%d\n",now);
	return 0;
}

T3 高级打字机(洛谷 p1383)

题目描述

jzoj 3794 高级打字机
洛谷 p1383 高级打字机

早苗入手了最新的高级打字机。最新款自然有着与以往不同的功能,那就是它具备撤销功能,厉害吧。
请为这种高级打字机设计一个程序,支持如下3种操作:
T x:在文章末尾打下一个小写字母x。(type操作)
U x:撤销最后的x次修改操作。(Undo操作)(注意Query操作并不算修改操作)
Q x:询问当前文章中第x个字母并输出。(Query操作)
文章一开始可以视为空串。

Input
第1行:一个整数n,表示操作数量。
以下n行,每行一个命令。保证输入的命令合法。

Output
每行输出一个字母,表示Query操作的答案。

Sample Input
7
T a
T b
T c
Q 2
U 2
T c
Q 2

Sample Output
b
c

Data Constraint
对于40%的数据 n<=200;
对于100%的数据 n<=100000;保证Undo操作不会撤销Undo操作。
<高级挑战>
对于200%的数据 n<=100000;Undo操作可以撤销Undo操作。
<IOI挑战>
必须使用在线算法完成该题。

notes&keys

某不知名巨佬:
“这道题不就是用下数据结构就好了吗?”
我:“????”
巨佬:
不是道板子题么?就是用主席树维护一下,就没了啊,有什么好讲的吗?
我:
“?????????????????????”(目瞪狗呆)

啊主席树…
awsl…
居然两百分儿一道题(# ̄~ ̄#)…

100分做法…

  • 线段树
    每个节点记录区间中有多少字母,
    查询:like权值线段树查第k大。

200分做法…

  • 可持久化线段树…
    可持久化线段树再加上前面的做法。

纪中提供题解

这道题的意思是,你的程序就像一台打字机,有3种操作方法:
T x:在末尾加上x
U x:撤销x次操作(100分以上的点还会撤销Undo操作)
Q x:输出这个字符串的第x个字符
总共有n次操作,输出每次Q询问的结果

100分:像一个栈一样,T就插入x,U就删除栈顶-x到栈顶个字符

180分:建立一个[1…100000]的ansistring数组,用来保存每一个版本的文章,再用ans记录总共有多少个版本,T的话就版本数+1,并且新的版本就等于上一个版本的末尾+x,如果是U的话,其实就是取第ans-x-1个版本,Q就是输出当前的版本的第k个字符,

200分:很明显,因为是1~100000,而且是ansistring,所以会空间超限,因此,我用了一个循环数组(也就是滚动数组)来优化空间,这个滚动数组空间是20000,为什么呢,因为小了的话就会答案错误(U
x中的x),大了的话就容易空间超限,Q操作一样,就是ans=ans mod
20000+1,T操作ans-1要进行特殊处理,ans=1到数组的末尾,U操作ans-x-1也要特殊操作,转到a[20000-x-1+ans],然后就AC了。

由于没有学过主席树而改题失败的某蒟蒻…o(╥﹏╥)o
此处只能附上纪中大佬的标程~


#include <cstdio>
#include <cstring>

const int N = 100007;

int q, t, cnt = 0, tot = 0;
char c;

int len[N];

struct Tree
{
    int rt[N], lson[N << 5], rson[N << 5];
	char val[N << 5];

    void build(int &rt, int fa, int l, int r, int po)
    {
        if (!rt)
            rt = ++tot; //???????
        if (l == r)
        {
            val[rt] = c; //??????????
            return;
        }
        int mid = (l + r) >> 1;
        if (po <= mid)
            rson[rt] = rson[fa], build(lson[rt], lson[fa], l, mid, po);
        if (mid + 1 <= po)
            lson[rt] = lson[fa], build(rson[rt], rson[fa], mid + 1, r, po);
    }

    char qry(int rt, int l, int r, int po)
    {
        if (l == r)
            return val[rt]; //???????????
        int mid = (l + r) >> 1;
        if (po <= mid)
            return qry(lson[rt], l, mid, po);
        if (mid + 1 <= po)
            return qry(rson[rt], mid + 1, r, po);
    }
} tree;

int main()
{
    scanf("%d", &q);
    while (q--)
    {
        scanf(" %c", &c);
        if (c == 'T')
        {
            scanf(" %c", &c);
            len[++cnt] = len[cnt - 1] + 1; //???????????????????
            tree.build(tree.rt[cnt], tree.rt[cnt - 1], 1, N, len[cnt]);
        }
        else if (c == 'U')
        {
            scanf("%d", &t);
            len[++cnt] = len[cnt - t - 1]; //?????cnt - t - 1???????
            tree.rt[cnt] = tree.rt[cnt- t - 1];
        }
        else
        {
            scanf("%d", &t);
            printf("%c\n", tree.qry(tree.rt[cnt], 1, N, t)); //??????????
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值