目录
前言
回文游戏
分析
代码
牛奶交换
分析
代码
最大限度的提高生产力
分析
代码
幸运日!!!
遇到三个非常简单的题。几乎没费什么脑子就写出来了。
本来今天是不打算再写的,因为做完实验本身就已经十点了,而且实验过程并不愉快(一条双绞线裁了九次……不过最终主播在朋友的帮助下还是成功做出来了——皆大欢喜)。
还有回寝室的时候遇到一只野生哈基米,好可爱^_^
今日事今日毕。事不宜迟我们马上开始吧!
首先看到(最优策略,绝顶聪明)等词就能想到这是一道博弈论问题了,于是我们开始按照博弈论的思路去思考。
分析先手必败状态,很明显0
是先手必败状态。
随后1~9
的个位数都是回文数,可以直接拿完,所以说都是先手必胜状态。
再之后是10
,肯定是不可以直接拿完的,因为10
不是一个回文数(题目说了前导零不属于回文数),所以只能拿个位数,而拿完个位数之后剩下的数也是一个个位数——进入先手必胜状态。随后对手拿一个个位数我们就输了。
所以10
是一个先手必败状态,而因为前导零不属于回文数,所以所有以0
结尾的数字都不是回文数(0
除外)
而0
又是一个先手必败状态,所以是不是只要最后一个数字是0
,那么他就是一个先手必败状态呢?反之只要结尾不是0
他就是先手必胜状态。
很显然是正确的,为什么?
老规矩,先证明所有的先手必胜状态都能转换成先手必败。
很显然这也是成立的,为什么?
因为个位数一定是回文数,每次只拿一个个位数最后一定可以将非0
的最后一位转化成0
再证明先手必败状态无论如何都会转化成先手必胜状态。
我们知道若想保留先手必败状态就需要拿一个10
的倍数,这显然是不可能的,因为所有以0
结尾的数字都不是回文数。
至此,证明完成。
#include
using namespace std;
const int N = 100010;
int t;
string s;
int main()
{
scanf("%d", &t);
while(t--)
{
cin >> s;
if(s.back() == '0')
cout << "E\n";
else
cout << "B\n";
}
return 0;
}
初看没有什么思路,但是分析一下的话就很容易得出来如果两头奶牛是相对的话——RL
,这样他们的牛奶就永远不会减少。
随后再分析LLLLL
这样的序列,可以发现这样的情况也是永远不会减少的。
但是其他的牛如何分析呢?
再观察,有的牛是最开始就会减少的比如LRR
的中间这头牛,无私奉献却没有半点回报,而右边这头牛是只有在中间的牛没有牛奶后才会减少的——唇亡齿寒啊,所谓敌不动我不动,好像也可以这样来讲。那么如何分析呢?
我先来讲一个小细节,那就是所有的牛奶都是动态的而不是静态的,有的桶会丢弃牛奶的同时灌入牛奶,这让我相当了昨天看到的一个科普知识——虚拟内存。原理很简单,占用内存的同时释放内存,这样内存就会无穷无尽(假装有很大的内存,将所有可输出的牛奶视作虚拟容量)。可以观察到和这道题的过程很像,所以我自然而然的想到了一种策略——虚拟容量,即每个桶的容量都是虚拟的,给每个桶设置一个虚拟容量的属性,然后记录所有进入桶的牛奶数量(最开始的也算),最后减去M
就好了。
有了这种策略就可以很方便的推算出最终的结果。但是问题又来了,从哪里开始记录呢?
当时是从起点,即——只出不进的牛,等等,只出不进的牛?这不就是入度为0
?还有前面那个不会减少的状态好像也是环……再加上每头牛之间你不动我不动的关系……
想到这里,主播恍然大悟,就决定是你了!拓补排序!
结果很明了了,建图,随后拓补排序的过程中记录虚拟容量,随后再通过M
推导出容量即可!
/*
写着写着发现可以抽象成图,然后判环,环内的所有元素必定是无穷的。
紧接着就有一个问题?拓补排序能不能处理环?
其实是可以的,因为环上的元素根本不会进拓补排序。
再加上我前面所说的虚拟容量,结果就很清晰了
*/
#include
#include
using namespace std;
const int N = 200010;
typedef long long LL;
int n, m;
char s[N];
LL a[N], b[N]; //有可能会超范围, 然后存一个蓝本来记录会不会溢出
int p[N];
LL l2; //存储总量的
queue que;
int next(int i)
{
if(s[i] == 'R') return (i + 1) % n;
return (i - 1 + n) % n;
} //模拟一个环
int main()
{
cin >> n >> m;
cin >> s;
for(int i = 0; i < n; i++)
{
cin >> a[i];
b[i] = a[i];
int x = next(i); //取出指向位置
p[x]++; //被指向的位置增加入度值
}
for(int i = 0; i < n; i++)
{
if(p[i] == 0)
que.push(i); //添加
}
while(que.size())
{
int x = que.front(); que.pop(); //取出来了
if(--p[next(x)] == 0) //入度为0
{
int y = next(x);
que.push(y);
a[y] += a[x]; //记录虚拟容量
}
}
for(int i = 0; i < n; i++)
{
if(p[i] != 0) //成环了
l2 += a[i];
else
l2 += min((LL)b[i], max((LL)0, a[i] - m));
}
cout << l2;
return 0;
}
看到这道题的时候就有一种熟悉的感觉,之前是不是有一道给定每根芦笋的高度排名和生长趋势,随后将问题转化成不等式求值问题?
观察t + s
,对于每次访问若想成功就要满足题目说的小于关门时间,即:t + s < c
,而t
和c
都是已知的,可以发现我们可以表示出s
,即:
s < c - t
,即若想访问到这个节点s
就必须要小于c - t
,我们将这些s
存起来,排个序(推荐采用从大到小排序的策略。)
随后对于每次询问若想求能够进行多少次访问只需要求我们存储的s
中有多少是大于当前给定时间的就可以了。
而对于线性元素的查找我们是不是可以采用二分或者map
?到这里这道题就解出来了,是不是很简单。
来分析一下时间复杂度。
不等式求值O(n)
,排序O(nlogn)
,而对于每次询问是O(mlogm)
,m = 1e6
,logm
约等于20
,20 * 1e6 = 2e7 < 1e8
。所有最终可以通过。
/*
这是一个离线问题,肯定是要打表的。
给定t1和s,就是给定访问的时间,转化成n个不等式
将每次访问的最高限度时间存储起来随后每次询问二分。
s = 1e6, logs 约等于20, 20 * 1e6 < 1e8,是可以求解的
t + s < c
s < c - t;
*/
#include
#include
using namespace std;
const int N = 200010;
int n, q;
int c[N];
int find(int s)
{
int l = -1, r = n;
while(l < r)
{
int m = (l + r) >> 1;
if(s >= c[m])
r = m;
else
l = m + 1;
}
return l;
} //查找的是闭区间,即小于等于s的所有位置
bool cmp(int x, int y)
{
return x > y;
}
int main()
{
cin >> n >> q;
for(int i = 0; i < n; i++) cin >> c[i];
for(int i = 0; i < n; i++) {int x; cin >> x; c[i] -= x;}
sort(c, c + n, cmp); //排序
//for(int i = 0; i < n; i++) cout << c[i] << ' ';
while(q--)
{
int s, v;
cin >> v >> s;
int x = find(s);
//cout << x << endl;
if(x >= v) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}