|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
using namespace std;
|
|
|
typedef long long LL;
|
|
|
const int N = 2000010; // 破环成链,双倍长度
|
|
|
int n, p[N], d[N]; // n:油站数量,p:加上的油量,d:消耗掉的油量
|
|
|
LL s[N]; // 顺时针:p[i-1]-d[i-1] 的前缀和,逆时针:p[i + 1] - d[i] 的后缀和
|
|
|
int q[N], hh, tt; // 队列
|
|
|
bool ans[N]; // 结果数组,因为每个站点都有一个结果:是不是能从它出发环行一周,所以,需要一个结果数组
|
|
|
|
|
|
int main() {
|
|
|
// 此题目数据量 n<=1e6,数据量大,使用scanf进行读取
|
|
|
scanf("%d", &n);
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
scanf("%d %d", &p[i], &d[i]);
|
|
|
// 破环成链
|
|
|
p[i + n] = p[i]; // 在i点的加油量
|
|
|
d[i + n] = d[i]; // ① 顺时针:从i到下一站i+1的耗油量,②逆时针:从i+1到下一站i的耗油量
|
|
|
}
|
|
|
|
|
|
// 一、顺时针
|
|
|
// (1) 前缀和
|
|
|
for (int i = 1; i <= n * 2; i++) s[i] = s[i - 1] + p[i - 1] - d[i - 1];
|
|
|
|
|
|
// 每个节点考查它右侧最长n个长度的窗口中,s[j]的最小值=s[q[hh]]
|
|
|
// 它右边i+1,i+2,...,i+n需要先入队列,才能让i看到未来,倒序遍历
|
|
|
// (2) 哨兵
|
|
|
q[0] = n * 2 + 1; // 倒序遍历,添加右侧哨兵
|
|
|
hh = 0, tt = 0; // 单调队列
|
|
|
for (int i = n * 2; i; i--) { // 倒序遍历
|
|
|
while (hh <= tt && q[hh] - i > n) hh++; // 最长n个站点
|
|
|
/*
|
|
|
① 如果最小值都大于s[i],说明i可以环形完成旅行
|
|
|
② 走到i时,没加上i站点的油前,考查前i-1,i-2,..,i-n的情况,i还没有参加讨论,所以先用队列解决问题后,再将i入队列
|
|
|
*/
|
|
|
if (s[q[hh]] >= s[i]) ans[i] = true; // s[q[hh]]=s[j]区间内最小值,s[j]-s[i]>=0就是可以走到
|
|
|
while (hh <= tt && s[q[tt]] >= s[i]) tt--; // 准备i入队列,保留年轻+漂亮(数值更小),喜新厌旧,什么东西!
|
|
|
q[++tt] = i; // i入队列
|
|
|
}
|
|
|
|
|
|
// 二、逆时针,后缀和
|
|
|
for (int i = 2 * n; i; i--) s[i] = s[i + 1] + p[i + 1] - d[i]; // 这里有一个细节,d[i]其实就是i+1->i消耗掉的油量
|
|
|
q[0] = 0; // 正序遍历,添加左侧哨兵
|
|
|
hh = 0, tt = 0; // 初始化队列
|
|
|
for (int i = 1; i <= 2 * n; i++) { // 正序遍历
|
|
|
while (hh <= tt && i - q[hh] > n) hh++; // 最长n个站点
|
|
|
if (s[q[hh]] >= s[i]) ans[i - n] = true;
|
|
|
while (hh <= tt && s[q[tt]] >= s[i]) tt--;
|
|
|
q[++tt] = i;
|
|
|
}
|
|
|
|
|
|
// 输出
|
|
|
for (int i = 1; i <= n; i++) puts(ans[i] ? "TAK" : "NIE");
|
|
|
return 0;
|
|
|
} |