JOISC 2020 Day3
T1 Constellation3
原题
CSDN下载:https://download.csdn.net/download/Ljnoit/12262096
链接
翻译
题目描述
JOI 君拍了一张 的星空图,将左起第 列,下起第 行的像素点称为像素 。
画面里有白色的大楼,黄色的星星,黑色的空格。第 列从最下方到自下数起第 行都是白色的大楼。有 个星星,第 个星星位于像素点 。此外,所有的像素点都是黑色。
若一个长方形区域可以称作星座,则满足以下条件:
- 1.不含白色像素点。
- 2.至少存在两个星星。
看厌了星座的 JOI 君要把一些黄色的星星涂成黑色,使得没有星座存在。将第 个星星涂成黑色会使照片的不自然度增加 ,最初不自然度为 。求不自然度的最小值。
输入格式
输入第一行为一个整数 ,表示地图的边长大小。
第二行为 个整数 ,描述如题目。
第三行为一个整数 ,表示星星的个数。
接下来的 行,每行三个整数 ,即对第 个星星的描述。
输出格式
输出不自然度的最小值。
样例输入 1
5
1 3 4 2 3
3
1 5 3
4 3 2
2 4 2
样例输出 1
2
样例解释 1
可以发现把第三个星删了之后它就和一号构不成星座,且比删去一号花费少。
样例输入 2
7
5 6 2 3 6 7 6
5
7 7 5
3 3 7
3 7 10
1 7 6
4 7 8
样例输出 2
16
样例解释 2
删去三号和四号。
数据范围
对于 % 的数据, ,保证:
- ;
- ;
- ;
- ;
- 。
详细子任务及附加限制如下表:
子任务编号 | 附加限制 | 分值 |
---|---|---|
1 | 14 | |
2 | 21 | |
3 | 无附加限制 | 65 |
解析
法一:
笛卡尔树+线段树合并
对于一个区间[li,ri],找到最高点a[x]。
这个区间只能有一个
的点。
暴力就是先建出笛卡尔树:
设
表示
里选的最高点是j的最大和。
转移有:
这里选一个点
,左区间选一个
,右区间选一个
。
可以一个接一个合并,复杂度
。
用线段树合并优化一下就O(
)
法二:
笛卡尔树+树状数组+dfs序
考虑把笛卡尔树建成一个树形关系。
如果在
这里选了一个
,选
就相当于覆盖了
到
的一个祖先
(
且
)。
可以倍增求出
。
问题变为一棵树上有若干祖先后代链,选出不相交的若干链,使权值和最大。
经典问题(不是祖先后代链也能做),只需要dfs序+树状数组实时维护一个点到根的dp值的和就差不多了。
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
namespace IO {
#include <cctype>
template <typename T>
inline void read(T &x){
x=0;
char c=0;
T w=0;
while (!isdigit(c)) w|=c=='-',c=getchar();
while (isdigit(c)) x=x*10+(c^48),c=getchar();
if(w) x=-x;
}
template <typename T>
inline void write(T x) {
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else write(x/10),putchar(x%10+'0');
}
template <typename T>
inline void writeln(T x) {
write(x);
putchar('\n');
}
}
using IO::read;
using IO::write;
using IO::writeln;
const int N=2e5+5;
struct Node {
int f[N];
int getfa(int x) {
return x==f[x] ? x : f[x]=getfa(f[x]);
}
void init(int n) {
for(int i=1; i<=n+1; i++) f[i]=i;
}
void merge(int x,int y) {
int fx=getfa(x),fy=getfa(y);
if(fx!=fy) f[fx]=fy;
}
} f1,f2;
int n,m;
LL a[N],c[N];
vector <int> v[N];
vector <PII> s[N];
inline void add(int x,LL g) {
while(x<=n+1) {
c[x]+=g;
x+=(x&(-x));
}
}
inline LL ask(int x) {
LL ret=0;
while(x) {
ret+=c[x];
x-=(x&(-x));
}
return ret;
}
int main() {
read(n);
f1.init(n);
f2.init(n);
for(int i=1; i<=n; i++) {
read(a[i]);
v[a[i]].push_back(i);
}
read(m);
int x,y,z;
for(int i=1; i<=m; i++) {
read(x); read(y); read(z);
s[y].push_back(make_pair(x,z));
}
LL ans=0;
for(int i=1; i<=n; i++) {
for(auto r:s[i]) {
LL tmp=ask(r.first);
if(r.second>=tmp){
ans+=tmp;
add(f1.getfa(r.first)+1,r.second-tmp);
add(f2.getfa(r.first),-r.second+tmp);
}
else ans+=r.second;
}
for(auto r:v[i]) {
f1.merge(r,r-1);
f2.merge(r,r+1);
}
}
writeln(ans);
return 0;
}
T2 Harvest
原题
CSDN下载:https://download.csdn.net/download/Ljnoit/12262096
链接
翻译
题目描述
IOI 庄园有 个员工, 棵苹果树种在湖岸。湖的周长为 米。
一开始员工 位于从湖的最北端向顺时针方向前进 米处,所有 互异。苹果树 生长在从湖的最北端向顺时针方向前进 米处,所有 互异。
每棵苹果树最多长一个苹果,收获后 秒会长出一个新的。时刻 时,所有的苹果树上都有一个苹果。员工从时刻 开始从各自的地点以 的速度顺时针前进,遇到成熟的苹果就将其摘下(若到达时刚长出苹果,也要摘下),摘苹果的时间忽略不计。
现给出 个询问,第 次询问员工 在时刻 结束时一共收获到几个苹果。
输入格式
输入第一行为四个整数 ,意义由题面所示。
第二行为 个整数 。
第三行为 个整数 。
第四行为一个整数 ,即询问的数量。
接下来的 行,每行两个整数 。
输出格式
输出共 行,第 行输出一个整数为第 个问题的答案。
样例输入 1
3 2 7 3
1 4 6
0 5
3
1 7
2 3
3 8
样例输出 1
2
1
1
样例解释 1
在第 1 个时刻,员工 2 从第 2 棵苹果树上收获了苹果,员工 3 从第 1 棵苹果树上收获了苹果。
在第 3 个时刻,员工 2 到达了第 1 棵苹果树。但是因为那时树上没果子所以没收获。
在第 4 个时刻,员工 1 从第 2 棵苹果树上收获了苹果。
在第 6 个时刻,员工 1 从第 1 棵苹果树上收获了苹果,员工 3 到达了第 2 棵苹果树,但还是因为那时树上没果子所以没收获。
在第 8 个时刻,员工 2 从第 2 棵苹果树上收获了苹果,员工 3 到达了第 1 棵苹果树,但再一次因为那时树上没果子所以没收获。
到第 7 个时刻为止,员工 1 收获了 2 个苹果,故第一行输出 2。
样例输入 2
5 3 20 6
0 4 8 12 16
2 11 14
9
4 1932
2 93787
1 89
5 98124798
1 2684
1 137598
3 2
3 8375
4 237
样例输出 2
146
7035
7
7359360
202
10320
0
628
18
样例输入 3
8 15 217 33608
0 12 71 96 111 128 152 206
4 34 42 67 76 81 85 104 110 117 122 148 166 170 212
14
2 223544052420046341
3 86357593875941375
4 892813012303440034
1 517156961659770735
7 415536186438473633
6 322175014520330760
7 557706040951533058
6 640041274241532527
5 286263974600593111
8 349405886653104871
1 987277313830536091
5 989137777159975413
2 50689028127994215
7 445686748471896881
### 样例输出 3
33230868503053
3
5
1
123542793648997
8
165811220737767
8
7
1
1
7
7535161012043
132506837660717
数据范围
对于 100% 的数据, , , , ,保证:
- ;
- ;
- ;
- ;
- ;
- ;
- 。
详细子任务及附加限制如下表:
子任务编号 | 附加限制 | 分值 |
---|---|---|
1 | 5 | |
2 | 20 | |
3 | 无附加限制 | 75 |
解析
方法:基环树
考虑对每一个点求出fa[i]表示i取了一个果子后,下一个取这个果子的是谁。
因为每个点一条出边,所以是个基环内向树。
再处理一下每个果子第一次被谁吃,然后把每个果子的时间改为被第一个人吃的时间+到树根的时间。
用个线段树合并再查询一下得到树上的答案。
接着考虑环上的答案。
先破环为链,记一个前缀和数组s[x]表示从x走到1的答案,设环长是len。
x,y都是环上的点:
假设y子树里的一个果子到y的时间是T1,而x处有一个询问T2。
设
那么贡献就是:
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
namespace IO {
#include <cctype>
template <typename T>
inline void read(T &x){
x=0;
char c=0;
T w=0;
while (!isdigit(c)) w|=c=='-',c=getchar();
while (isdigit(c)) x=x*10+(c^48),c=getchar();
if(w) x=-x;
}
template <typename T>
inline void write(T x) {
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else write(x/10),putchar(x%10+'0');
}
template <typename T>
inline void writeln(T x) {
write(x);
putchar('\n');
}
}
using IO::read;
using IO::write;
using IO::writeln;
const int N=2e5+5;
const LL inf=2e18;
struct Node {
int ch[2];
int sum;
};
class sgt {
public:
Node t[20000000];
int nodecnt;
void init() {
nodecnt=0;
}
int newnode() {
int p=++nodecnt;
t[p].ch[0]=t[p].ch[1]=t[p].sum=0;
return p;
}
void insert(int &p,LL l,LL r,LL pos) {
if(!p) p=newnode();
t[p].sum++;
if(l==r) return;
LL mid=(l+r)>>1;
if(pos<=mid) insert(t[p].ch[0],l,mid,pos);
else insert(t[p].ch[1],mid+1,r,pos);
}
int query(int p,LL l,LL r,LL ql,LL qr) {
if(!p) return 0;
if(ql<=l && r<=qr) return t[p].sum;
LL mid=(l+r)>>1;
int ret=0;
if(ql<=mid) ret=query(t[p].ch[0],l,mid,ql,qr);
if(qr>mid) ret+=query(t[p].ch[1],mid+1,r,ql,qr);
return ret;
}
int merge(int x,int y) {
if(!x || !y) return x|y;
t[x].sum+=t[y].sum;
t[x].ch[0]=merge(t[x].ch[0],t[y].ch[0]);
t[x].ch[1]=merge(t[x].ch[1],t[y].ch[1]);
return x;
}
} T;
vector<int> pt1;
vector<int> stapple[N],t[N];
vector<pair<int,LL> > query[N];
int n,m,L,C,bans,bane;
int a[N],b[N],Q,fa[N],d[N],vis[N],rt[N];
LL dep[N],ans[N];
void dfs(int np) {
vis[np]=1,pt1.push_back(np);
for(int &x:stapple[np]) T.insert(rt[np],0,inf,x+dep[np]);
for(int &x:t[np]) {
if(np==bans && x==bane) continue;
dep[x]=dep[np]+d[x];
dfs(x);
rt[np]=T.merge(rt[np],rt[x]);
}
for(auto &x:query[np])
ans[x.first]=T.query(rt[np],0,2e18,dep[np],dep[np]+x.second);
}
void solve(int p) {
T.init();
int cx=p;
pt1.clear();
while(!vis[cx]) vis[cx]=1,cx=fa[cx];
bans=fa[cx],bane=cx,dfs(cx);
LL totlen=dep[bans]+d[cx];
T.init();
vector<LL> cur;
for(int &x:pt1) for(int &y:stapple[x])
cur.push_back(y+dep[x]+d[cx]+dep[bans]);
sort(cur.begin(),cur.end());
vector<pair<LL,LL> > c2;
for(int i=bans; i; i=fa[i]) {
for(auto &t:query[i])
c2.push_back(make_pair(t.second+totlen+dep[i],t.first));
if(i==bane) break;
}
sort(c2.begin(),c2.end());
LL vl=0;
int R=0;
for(int i=0,j=0; i<(int)c2.size(); i++) {
while(j<(int)cur.size() && cur[j]<=c2[i].first) {
T.insert(R,0,totlen,cur[j]%totlen);
vl+=cur[j]/totlen;
j++;
}
ans[c2[i].second]-=vl;
ans[c2[i].second]+=1LL*(c2[i].first/totlen)*T.query(R,0,totlen,0,c2[i].first%totlen);
ans[c2[i].second]+=1LL*(c2[i].first/totlen-1)*T.query(R,0,totlen,c2[i].first%totlen+1,totlen);
}
}
int main() {
read(n);
read(m);
read(L);
read(C);
for(int i=1; i<=n; i++) {
read(a[i]);
a[i]=L-1-a[i];
}
for(int i=1; i<=m; i++) {
read(b[i]);
b[i]=L-1-b[i];
}
reverse(a+1,a+n+1);
reverse(b+1,b+m+1);
for(int i=1; i<=n; i++) {
int nxt=(a[i]+C)%L;
if(nxt>a[n])fa[i]=1,d[i]=L+a[1]-nxt+C;
else fa[i]=lower_bound(a+1,a+n+1,nxt)-a,d[i]=a[fa[i]]-nxt+C;
}
for(int i=1; i<=m; i++) {
if(b[i]>a[n]) stapple[1].push_back(a[1]+L-b[i]);
else {
int cur=lower_bound(a+1,a+n+1,b[i])-a;
stapple[cur].push_back(a[cur]-b[i]);
}
}
read(Q);
for(int i=1; i<=Q; i++) {
int p;
LL T;
read(p);
read(T);
query[n+1-p].push_back(make_pair(i,T));
}
for(int i=1; i<=n; i++) t[fa[i]].push_back(i);
for(int i=1; i<=n; i++)
if(!vis[i]) solve(i);
for(int i=1; i<=Q; i++) writeln(ans[i]);
return 0;
}
T3 Stray
原题
CSDN下载:https://download.csdn.net/download/Ljnoit/12262096
链接
翻译
题目描述
这是一道通信题。
Anthony 是一只生活在 JOI 市的蚂蚁。JOI 市共有被划分为 个町,编号为 到 。Anthony 居住在 号町。总共有 条路,编号为 到 。第 条路连接编号为 和 的町 ( ),每条路是双向的。保证不存在两条连接两个相同的町的道路,同时每个点可以直接或间接互相到达。
Catherine 是一只猫,也是 Anthony 的好朋友。她打算游览 JOI 市,但是她并不知道关于路的信息,所以经常迷路。于是,Anthony 打算事先在每条道路上都作上标记。标记共有 种,编号为 到 。
Catherine 现在到达了 JOI 市。当她不在 号町时,她会做如下事情:
- 对于每种标记,她会统计当前所在的町连出去的道路中,除了她上一次走来的道路(若存在)之外,这种标记共出现了多少次。
之后她会选择一条路去行动。注意除了上一次走来路,她仅能通过路上的标记来分辨其余的道路。她想尽量快地到达 号町。更准确地说,若起点至 号町最少需要经过 条道路,那么她希望在经过不超过 条边后就能到达 号町。
你需要编写程序模拟 Anthony 摆放路标和 Catherine 探索 JOI 城的过程。
交互细节
你需要提交两个程序。
第一个程序为Anthony.cpp
,它会模拟Anthony的摆放过程。需要包含头文件Anthony.h
。
-
std::vector<int> Mark(int N, int M, int A, int B, std::vector<int> U, std::vector<int> V)
该程序会恰好在开始时调用一次。- 数字 含义同题面。
- 存储着所有的边, 表示第i条路连接的街道 。
选手需要返回一个长度为 的数组 ,第 个元素 代表在第 条道路上的路标编号。
若 长度不为 ,测评结果为 “Wrong Answer [1]”。若不满足 ,测评结果为 “Wrong Answer [2]”。
第二个程序为Catherine.cpp
,它会模拟Catherine的探索过程。需要包含头文件Catherine.h
。
-
void Init(int A, int B)
该程序会恰好在开始时调用一次。- 数字 含义同题面。
-
int Move(std::vector<int> y)
该程序会在每次Catherine到达一个不是0的街道时调用。- y 是一个长度为A的数组,元素 yj 代表着所有与当前街道相连且不为她上一次走过的路(若存在)的路上标记 j 的出现次数。
- 你需要返回一个整数 ,表示选择走的标记。需要满足 。若不满足 ,测评结果为 “Wrong Answer [3]”。若 ,表示Catherine原路返回,此时若Catherine未进行过任何操作,测评结果为 “Wrong Answer [4]”。若 ,表示 Catherine 选择一条标记为 z 的边经过,此时若 ,测评结果为 “Wrong Answer [5]”。
注意如果有多条可以选择的边,
grader
不一定会随机选择一条合法的出现行动。如果 Catherine 未能在 步内到达街道0,测评结果为 “Wrong Answer [6]”。
注意事项
选手程序可能会运用若干全局变量和子程序。为了防止多个文件变量重名或子程序重名带来的编译错误,请将所有全局变量和子程序定义在一个没有名字的 namespace 里。
如果在程序里使用 printf/scanf/cout/cin
,会直接导致Wrong Answer 或者 Runtime Error。
最终测评时会将 Anthony 和 Catherine 的程序独立编译,因此不能共享全局变量。
编译与运行
将grader.cpp
, Anthony.cpp
,Catherine.cpp
,Anthony.h
,Catherine.h
放在同一文件夹下,在终端内运行如下命令
g++ -O2 -o grader grader.cpp Anthony.cpp Catherine.cpp
编译成功时会得到一个可执行文件grader
。
可执行文件从标准输入种读入以下内容:
第一行五个数字
。其中
表示起点。
接下来
行,每行两个数字
,表示一条道路。
可执行文件回想标准输出流输出以下内容。
若程序运行时产生错误 [1] 至 [5],则会输出形如 “Wrong Answer [1]” 的错误信息。
否则如果未在 N+B 步内到达街道1,输出 “Wrong Answer; Number of moves > N + B”.
否则按照 “Number of moves = 4” 的格式输出信息。
数据范围
- 子任务1 (2分): 。
- 子任务2 (2分): 。
- 子任务3 (2分): 。
- 子任务4 (9分): 。
- 子任务5 (5分): 。
- 子任务6 (71分): 。
- 子任务7 (9分): 。
对于所有测试数据,满足 。
保证图联通且无重边无自环。
时间限制:2s
空间限制:1GB
解析
对于第一类子任务。我们只能走最短路。
考虑求出这张图的 BFS 树,显然,所有边要么是连接相邻两层的点(异层边),要么是连接同层的点(同层边)。
如果没有同层边,那么我们只要对于每一层的边做同一种标记,按 0,1,2 循环标记即可。因为这样的话,在某一个点上,即使有两种存在的标记,我们也可以确定哪一个是往上走的。
那么如果有了同层边,其实也很简单,只要做它下面的那一层的边的标记即可。
这样我们依旧可以确定哪一条边是向上的。
对于第二类子任务,只有两种标记,但图是一个树,而且允许多走六步。
我们先考虑把树划分为若干条链,如果一个点有至少两个儿子,那么称之为分叉点。我们希望标记能够满足:
对于每个分叉点,到父亲的边和到儿子的边的标记不同。
每一条链上都是 110010 的循环位移。
那么对于代码 B,它先走三步,如果中途走到非链的部分就直接确定了方向。否则,它还在链上,而且可以确定周围长度为 5 的子串的样子,这样的话,一定可以确定它是在往上走还是往下走,这样也确定了方向,而且最多多走 6 步。
代码
Anthony.cpp:
#include "Anthony.h"
#include <bits/stdc++.h>
using namespace std;
vector<int> Mark(int n,int m,int A,int B,vector<int> U,vector<int> V) {
vector<int> dist(n, -1);
vector<vector<int> > G(n);
for (int i=0; i<m; i++) {
G[U[i]].push_back(V[i]);
G[V[i]].push_back(U[i]);
}
dist[0]=0;
queue<int> que;
que.push(0);
while(!que.empty()) {
int u=que.front();
que.pop();
for(int v:G[u]) {
if(dist[v]==-1) {
dist[v]=dist[u] + 1;
que.push(v);
}
}
}
vector<int> ret(m);
if(A>=3) {
for(int i=0; i<m; i++) {
ret[i]=min(dist[U[i]],dist[V[i]])%3;
}
} else {
const int arr[6]={1,1,0,0,1,0};
for (int i=0; i<n; i++) G[i].clear();
for (int i=0; i<m; i++) {
if(dist[U[i]]>dist[V[i]]) swap(U[i], V[i]);
G[U[i]].push_back(i);
}
function<void(int, int)> dfs=[&](int u,int fr) {
if((int)G[u].size() == 1) {
int p;
if(fr==-1 || ret[fr]==0) {
p = 0;
} else {
p = 2;
}
while((int)G[u].size()==1) {
int i=G[u][0];
int v=V[i];
u=v;
fr=i;
ret[fr]=arr[p];
p=(p+1)%6;
}
}
int c=(fr==-1 ? 1 : ret[fr]^1);
for (int i:G[u]) {
int v=V[i];
ret[i]=c;
dfs(v,i);
}
};
dfs(0,-1);
}
return ret;
}
Catherine.cpp:
#include "Catherine.h"
#include <bits/stdc++.h>
using namespace std;
namespace {
const int arr[6]={1,1,0,0,1,0};
int A,on,fir,last;
vector<int> vec;
vector<int> cs;
inline bool bad(vector<int> v) {
vector<int> al;
for(int i=0; i<6; i++) al.push_back(arr[i]);
for(int i=0; i<6; i++) al.push_back(arr[i]);
for(int i=0; i<8; i++) {
int flag=1;
for(int j=0; j<5; j++) if(al[i+j]!=v[j]) {
flag=0;
break;
}
if(flag) return true;
}
return false;
}
}
void Init(int _A, int B) {
A=_A;
on=0;
fir=1;
last=-1;
}
int Move(vector<int> y) {
if(A>=3) {
int cc=0;
for(int i=0; i<3; i++) cc+=(y[i]!=0 ? 1 : 0);
if(cc==1) {
if(y[0]) return 0;
else if(y[1]) return 1;
else return 2;
} else {
if(y[0] && y[1]) return 0;
else if(y[1] && y[2]) return 1;
else return 2;
}
} else {
if(on) {
if(y[0]==1 && y[1]!=1) return last=0;
else if(y[0]!=1 && y[1]==1) return last=1;
else return last^=1;
}
if(fir) {
fir=0;
if(y[0]!=1 && y[1]!=1) {
if(y[0]) last = 0;
else last = 1;
y[last]--;
cs=y;
vec.push_back(last);
return last;
}
if(y[0]==1) last=0;
else last = 1;
if(y[last^1]!=1) {
on=1;
return last;
}
y[last]--;
cs=y;
vec.push_back(last);
return last;
}
if(y[0]!=1 && y[1]!=1) {
on=1;
return -1;
}
if(y[0]==1 && y[1]==1) {
on=1;
return last^=1;
}
if(y[0]>1) {
on=1;
return last=1;
}
if(y[1] >1) {
on=1;
return last=0;
}
if(vec.size()<3) {
if(y[0]) last=0;
else last=1;
vec.push_back(last);
return last;
}
vector<int> v;
v.push_back(cs[0] ? 0 : 1);
for(int i=0; i<3; i++) {
v.push_back(vec[i]);
}
v.push_back(y[0] ? 0 : 1);
on=1;
if(bad(v)) {
return -1;
} else {
return last=v.back();
}
}
}