
日记
OI的乐趣何在?
- ctj
- 刷水题
- 卡常
- 和教练斗智斗勇
上午
考试。
私人考试链接
何谓 私人
?
他™把 1600
的题放T3?他™把 2700
的题放T1?
T1
是道最短路?如是。
用 basic_string
把每一个路径存下来,知周所众,basic_string
就是 string++
,所以它也有重载的 <
用来判断字典序。于是我就能搓一个 dijkstra
骗 。
然而它RE了。
T2
似乎有点思路?
我们把这个平方展开,变成这个样子: 。然后它要是整数,那么 就得是完全平方数,也就是求有多少个 满足 是完全平方数。
也是只能想到这里了。
T3
一看:先把暴力打了再说。
再看:分块?
也是直接打出来了。时间复杂度是 ,但是知周所众,我向来算不来时间复杂度,分块还那么难调,这样例还那么水。
然后就打出来过拍。
一看原题:1600
放T3?绿题放T3?
T4
一看:
os:高精度滚啊
下午
改题
T1
source solution
这就是所谓的 T1
。
看题先看数据范围(因为我有过很多次没看数据范围导致以为想的是错解的经历)。它说 ,但是 , 很小,所以我们应该在 上做文章。
我们可以先把询问离线,然后枚举终点 (为啥和题目里的变量名不一样,因为我写的代码里叫 ,为了好理解代码),建反图,先不管字典序的问题,在反图上dfs,处理出能到 的点。
然后就是tj里很让人摸不着头脑的话:对于每一个能到 的点,它到 的字典序最小的路径里,它的后继一定是唯一的,也就是他出边连到的点里字典序最小或是编号最小的。这不废话吗,你都最小了肯定唯一咯
然后知道了这个,我们就可以处理出它的后继,然后每一个点都建一条到他后继的边,那么就是一棵内向树。内向树可不好搞哦,所以我们要把他反过来,建成外向树。然后在这个树上再跑dfs,用一个栈记录经过的点,就可以得出答案。 因为实现实在太美所以放一下代码。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
#include<bits/extc++.h>
#pragma GCC optimize(2)
using namespace std;
const int maxn(3005);
const int maxq(4e5 + 5);
int n,m,q;
int ans[maxq];
bool vis[maxn];
int st[maxn];
void read(int &x)
{
x = 0;
int f = 1;
char ch = getchar();
while (!isdigit(ch)){f = ch == '-' ? -1 : 1; ch = getchar();}
while (isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
x *= f;
}
vector<int>g1[maxn],g2[maxn],g3[maxn];//g1是正图,g2是反图,g3是建出来的树
struct query{int u,v,k,id;};//离线存询问
vector<query>q1[maxn],q2[maxn];
void dfs1(int u)
{
vis[u] = 1;
for (auto v : g2[u])
if (!vis[v])
dfs1(v);
}
void dfs2(int u)//在树上跑dfs,记录答案
{
st[++st[0]] = u;
for (auto i : q2[u])
if (st[0] >= i.k)
ans[i.id] = st[st[0] - i.k + 1];
for (auto v : g3[u])
dfs2(v);
st[0] --;
}
signed main()
{
read(n),read(m),read(q);
int u,v,k;
for (int i(1); i <= m; i++)
{
read(u),read(v);//输入加建边
g1[u].push_back(v);//建正图
g2[v].push_back(u);//建反图
}
for (int i(1); i <= n; i++)
sort(g1[i].begin(),g1[i].end());//给每一条出边连到的点排序,为下面找字典序最小做铺垫(死去的阅读理解忽然开始攻击我)
for (int i(1); i <= q; i++)
{
read(u),read(v),read(k);
q1[v].push_back((query){u,v,k,i});
}
memset(ans,0xff,sizeof ans);
for (int ed(1); ed <= n; ed++)
{
for (int j(1); j <= n; j++)
vis[j] = 0,q2[j].clear(),g3[j].clear();//对于每一个ed重新建图
dfs1(ed);//先处理有哪些点可以到ed
for (int i(1); i <= n; i++)
{
if (vis[i] && i != ed)
{
for (auto v : g1[i])
{
if (vis[v])
{
g3[v].push_back(i);//建树
break;
}
}
}
}
for (auto i : q1[ed])//处理询问
q2[i.u].push_back(i);
dfs2(ed);
}
for (int i(1); i <= q; i++)
printf("%d\n",ans[i]);
return 0;
}
晚上
CF115E Linear Kingdom Races solution
这道题,本来想的dp方程似乎是对的,但是一看tj,没有一道是我的那种dp,也是推得出来。
设 表示考虑到第 条路, 全选的最大收益。那么对于 ,增加了修路的代价 ,获得了左端点在 ,右端点在 的比赛的收益。那么有转移: 。
但是我们只考虑了 的情况。对于 ,有 ,因为 就是 的答案。
这样子状态设计完了。但是,我们发现,这样子的空间复杂度是 的,时间复杂度更是来到了 ,这对于 肯定不行啊。
我们先优化空间。发现对于 ,它只会从 转移。而对于 ,我们可以通过记录 来转移,综上,它总是由 转移而来,所以我们可以通过滚动数组来优化空间到 。
然后现在该优化时间了。发现每多考虑一个 ,都是 ,结合上文的滚动数组可以发现是一个区间修改。而对于 ,相当于单点赋值。最后加上一个没考虑的 ,就可以了。
再贴一下代码:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
//依旧又臭又长
#include<bits/extc++.h>
#define int long long
#define ls (rt << 1)
#define rs (rt << 1 | 1)
using namespace std;
const int maxn = 2e5 + 5;
int n,m;
int a[maxn];
struct edge
{
int l,p;
edge *nxt;
}*head[maxn];//链式前向星(?)记录右端点在i的赛事
struct Nahida//线段树(别管结构体名字)
{
int l,r;
int val,lazy;
}tree[maxn << 2];
void push_up(int rt){tree[rt].val = max(tree[ls].val,tree[rs].val);}//维护区间最大值
void push_down(int rt)//lazy_tag下放
{
if (!tree[rt].lazy)
return;
tree[ls].val += tree[rt].lazy;
tree[rs].val += tree[rt].lazy;
tree[ls].lazy += tree[rt].lazy;
tree[rs].lazy += tree[rt].lazy;
tree[rt].lazy = 0;
}
void build(int l,int r,int rt)
{
tree[rt].l = l;
tree[rt].r = r;
if (l == r)
{
tree[rt].val = tree[rt].lazy = 0;
return;
}
int mid = (l + r) >> 1;
build(l,mid,ls);
build(mid + 1,r,rs);
}
void upd(int ql,int qr,int x,int rt = 1)
{
int l = tree[rt].l;
int r = tree[rt].r;
if (ql <= l && r <= qr)
{
tree[rt].val += x;
tree[rt].lazy += x;
return;
}
push_down(rt);
int mid = (l + r) >> 1;
if (ql <= mid)
upd(ql,qr,x,ls);
if (qr > mid)
upd(ql,qr,x,rs);
push_up(rt);
}
void adde(int l,int r,int p)//链式前向星(?)增添赛事
{
auto tmp = new edge;
tmp->l = l;
tmp->p = p;
tmp->nxt = head[r];
head[r] = tmp;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
int l,r,p;
for (int i = 1; i <= m; i++)
{
cin >> l >> r >> p;
adde(l,r,p);
}
build(1,n,1);
int ans = 0;
for (int i = 1; i <= n; i++)
{
upd(i,i,ans);//j = i时的单点修改
upd(1,i,-a[i]);//j < i和j = i的区间修改
for (auto j = head[i]; j; j = j->nxt)
upd(1,j->l,j->p);//增加赛事的收益
ans = max(ans,tree[1].val);//记录最大答案
}
cout << ans;
return 0;
}