JOISC 2020 Day2
T1 Chameleon
原题
CSDN下载:https://download.csdn.net/download/Ljnoit/12260005
链接
翻译
题目描述
在 JOI 动物园中,有着 只变色龙,编号为 。其中,有 只变色龙的性别为 ,其余 只的性别为 。
每只变色龙都有一个原色。关于原色的可公开情报如下:
- 所有性别为 的变色龙的原色不同。
- 对于每只性别为 的变色龙,都存在唯一的原色与其相同的变色龙,且性别为 。
现在,JOI 动物园迎来了恋爱的季节。每只变色龙都爱上了另一只变色龙。关于恋爱对象的可公开情报如下:
- 每只变色龙都很专一于唯一一只异性的变色龙。
- 一只变色龙和它的恋爱对象的原色不同。
- 不存在两只变色龙同时追求另一只变色龙。
你可以召集一部分变色龙来组织一场会议。对于一只参加会议的变色龙 ,令 为它的恋爱对象。 的肤色由以下方式决定:
- 如果 参加了这场会议,则 的肤色为 的原色。
- 如果 没参加这场会议,则 的肤色为 的原色。
一只变色龙的肤色可以在不同的会议间发生改变。对于你组织的一场会议,你可以得到场上所有变色龙的肤色的种类数。
由于变色龙也会感到厌烦,所以你最多只能组织
场会议。同时你需要根据你得到的信息,确定有哪些变色龙的原色相同。
请你写一个程序在组织不超过
场会议的前提下确定所有相同原色的变色龙。
实现细节
你需要提交一个文件。
这个文件的名字应为 chameleon.cpp
。这个程序应当包含 chameleon.h
,且其中需要实现以下函数:
void Solve(int N)
对于每组测试数据,保证这个函数会被恰好调用一次。- 其参数
N
为题目中的 ,性别为 $X 的变色龙的个数。
- 其参数
你的程序可以调用以下函数:
-
int Query(const std::vector &p)
你可以通过调用这个函数组织一场会议。- 参数 是参加这场会议的变色龙的列表。
- 返回值即为本场会议所有变色龙的肤色的种类数。
-
中的每个元素都应该是一个
内的整数,否则你的程序将会被判为
Wrong Answer [1]
。 -
中的元素不得重复,否则你的程序将会被判为
Wrong Answer [2]
。 - 你的程序不应调用
函数超过
次,否则你的程序将会被判为
Wrong Answer [3]
。
-
void Answer(int a, int b)
你可以通过调用这个函数回答一对原色相同的变色龙。- 参数 和 表示变色龙 的原色相同。
- 必须保证
,否则你的程序将会被判为
Wrong Answer [4]
。 - 你的程序不得以相同的 a 值或 b 值调用此函数两次及以上,否则你的程序将会被判为
Wrong Answer [5]
。 - 如果 a,b 的原色不同,你的程序将会被判为
Wrong Answer [6]
。
你的程序应当调用 Answer 函数恰好 N 次,否则你的程序将会被判为Wrong Answer [7]
。
实现提示
- 你的程序可以实现其他函数供内部使用,或使用全局变量。
- 你的程序不得访问标准输入输出流,也不得通过任何方法访问其他文件。不过,你的程序可以输出到标准错误流。
编译与运行
你可以在附加文件中下载到一个压缩文件,其中包含一个样例交互库以测试你的程序。这个压缩文件也包含了一个你应当提交的程序的样例。
样例交互库为 grader.cpp
。为了测试你的程序,请把 grader.cpp
,chameleon.h
,chameleon.cpp
放在同一目录下,并执行以下命令来编译你的程序:
g++ -std=gnu++14 -O2 -o grader grader.cpp chameleon.cpp
若编译成功,会在当前目录生成一个可执行文件 grader
。
请注意样例交互库并非实际交互库。样例交互库仅是由一个单一的,从标准输入流读入数据,并将结果输出到标准输出流的过程构成的。
输入格式
样例交互库从标准输入流读入以下数据:
第一行,一个整数
。
第二行,
个整数
,表示每只变色龙的性别,若其为
则性别为
,否则为
。
第三行,
个整数
,表示每只变色龙的原色,保证其在
内。
第四行,
个整数
,表示每只变色龙的恋爱对象。
输出格式
样例交互库向标准输入流输出以下数据:
若你的程序是正确的,样例交互库将以 Accepted: 100
的形式输出你调用 Query
的次数。
若你的程序被判为 Wrong Answer
,样例交互库将以 Wrong Answer [1]
的形式输出其类型。
若你的程序触发了不止一种 Wrong Answer
,样例交互库只会输出其中一种。
样例输入 1
4
1 0 1 0 0 1 1 0
4 4 1 2 1 2 3 3
4 3 8 7 6 5 2 1
样例调用
调用 | 子调用 | 返回值 |
---|---|---|
Solve(4) | ||
Query([]) | 0 | |
Query([6, 2]) | 2 | |
Query([8, 1, 6]) | 2 | |
Query([7, 1, 3, 5, 6, 8]) | 4 | |
Query([8, 6, 4, 1, 5]) | 3 | |
Answer(6, 4) | ||
Answer(7, 8) | ||
Answer(2, 1) | ||
Answer(3, 5) |
在附加文件中,sample-02.txt
满足第一个子任务的限制,sample-03.txt
满足第四个子任务的限制。
数据范围与提示
对于所有数据,满足:
- 。
- 。
- 。
- 对于每个 ,存在一个唯一的 满足 。
- 对于每个 ,存在一个唯一的 满足 。
- 。
- 。
- 。
- 。
详细子任务附加条件及分值如下表:
子任务编号 | 附加限制 | 分值 |
---|---|---|
1 | 4 | |
2 | 20 | |
3 | 20 | |
4 | 20 | |
5 | - | 36 |
解析
听说有一种奇怪的方法是输出1,但我们不希望这样做。
首先有一个暴力的做法,将任意两个点判断,可以得到与之相关的1或3只变色龙:
1只是两只变色龙相互喜欢,那么剩下那只就是颜色相同;
3只从3只选2只并和自己判断一次,结果为1的那次剩下的那个就是他喜欢的,然后将所有喜欢关系删掉后剩下的就是颜色相同。
但这样一开始需要 次的判断,考虑优化,如果将点划分成若干个集合,每一个集合内部没有特殊关系就可行了,然后就可以再集合中二分来查找了,那么直接对前 个点构成的图染成 种颜色( 种颜色容易超过次数),分别进行二分查找即可,次数是 的,常数要注意( 种颜色的要注意要取编号最小的颜色,不然会被卡)
代码
#include "chameleon.h"
#include <bits/stdc++.h>
#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;
const int N=1005;
vector<int> v,p[11],vec[N];
int vis[N],to[N],ans[N][2];
bool pd(vector<int> &k,int l,int r,int x) {
p[4].clear();
p[4].push_back(x);
for(int i=l; i<=r; i++) p[4].push_back(k[i]);
return Query(p[4])<p[4].size();
}
void find(vector<int> &a,int l,int r,int i) {
int rr=r;
while(1) {
r=rr;
if((vec[i].size()==3) || (!pd(a,l,r,i))) return;
while(l<r) {
int mid=(l+r)>>1;
if(pd(a,l,mid,i)) r=mid;
else l=mid+1;
}
vec[i].push_back(a[l]);
vec[a[l]].push_back(i);
l++;
}
}
void Solve(int n) {
n=n<<1;
for(int i=1; i<=n; i++) {
int flag=4;
for(int j=0; j<4; j++)
if((vec[i].size()==3) || (!pd(p[j],0,p[j].size()-1,i))) flag=MIN(flag,j);
else find(p[j],0,p[j].size()-1,i);
p[flag].push_back(i);
}
int t=0;
for(int i=1; i<=n; i++) {
if(vec[i].size()<3) continue;
vis[i]=1;
for(int j=0; j<2; j++)
for(int k=j+1; k<3; k++){
v.clear();
v.push_back(i);
v.push_back(vec[i][j]);
v.push_back(vec[i][k]);
if(Query(v)==1) {
to[i]=vec[i][3-j-k];
j=k=3;
}
}
if(!to[i]) to[i]=vec[i][0];
}
for(int i=1; i<=n; i++)
if(vis[i]) {
for(int j=0; j<3; j++)
if(vec[i][j]==to[i]) vec[i][j]=0;
for(int j=0; j<3; j++)
if(vec[to[i]][j]==i) vec[to[i]][j]=0;
}
for(int i=1; i<=n; i++)
if(vis[i]) for(int j=0; j<3; j++)
if((vec[i][j]) && (vec[i][j]<i) && (vec[vec[i][j]].size()==3)) {
ans[++t][0]=i;
ans[t][1]=vec[i][j];
}
for(int i=1; i<=n; i++)
if(vec[i].size()==1) {
if((vec[vec[i][0]].size()==1) && (i>vec[i][0])) continue;
ans[++t][0]=i;
ans[t][1]=vec[i][0];
}
for(int i=1; i<=(n>>1); i++) Answer(ans[i][0],ans[i][1]);
}
T2 Joitter2
原题
CSDN下载:https://download.csdn.net/download/Ljnoit/12260005
链接
翻译
题目描述
Joitter 是一款社交软件,你可以在这里和你的朋友分享你的高光时刻。
在 Joitter 中,你可以关注别的用户。举例来说,当用户 a 关注了另外一个用户 b ,用户 a 可以在时间轴上阅读用户 b 的帖子。在这种情况下,用户 b 有可能关注用户 a ,也可能不关注用户 a 。当然,用户 a 不能关注 Ta 自己或者关注用户 b 超过一次。
一共有 N 个用户已经开始使用 Joitter ,一开始他们没有关注任何其他用户。
从现在起,持续 M 天,在第 i 天会发生用户 Ai 关注用户 Bi 的事件(1≤i≤M)。
Joitter 官方正在计划在这 M 天中举行一场活动,这场活动有如下的步骤:
选择一个用户 x 。
同时选择一个被 x 关注的用户 y 。
选择一个用户 z ,要求满足 z 不是 x ,x 没有关注 z ,且 y 和 z 互相关注。
让 x 关注 z
重复上述步骤,直到无法选出三元组 (x,y,z)
Joitter 官方仍然还没有决定何时开始举办这个活动。所以他们想要知道,∀i∈[1,m],若活动在第 i 天开始,活动结束后每个用户关注其他用户数量和的最大值是多少。
输入格式
从标准输入中读入以下内容:
第一行两个整数 N,M;
接下来 M 行,每行两个整数 Ai,Bi。
输出格式
输出 行到标准输出,第 i 行输出若活动在第 i 天开始,活动结束后每个用户关注其他用户数量和的最大值是多少。
样例输入 1
4 6
1 2
2 3
3 2
1 3
3 4
4 3
样例输出 1
1
2
4
4
5
9
样例说明 1
第一天,用户 1 关注了用户 2。在这天活动结束的话,没有任何其他用户会关注其他人。所以总和是 1。
第二天,用户 2 关注了用户 3。在这天活动结束的话,没有任何其他用户会关注其他人。所以总和是 2。
第三天,用户 3 关注了用户 2。在这天活动结束的话,用户 1 会关注用户 3。所以总和是 4,并且它是总和的可能最大值。
第四天,用户 1 关注了用户 3。在这天活动结束的话,没有任何其他用户会关注其他人。所以总和是 4。
第五天,用户 3 关注了用户 4。在这天活动结束的话,没有任何其他用户会关注其他人。所以总和是 5。
第六天,用户 4 关注了用户 3。在这天活动结束的话,用户 1 会关注用户 4,用户 2 会关注用户 4,用户 4 会关注用户 2。所以总和是 9,并且它是总和的可能最大值。
样例输入 2
6 10
1 2
2 3
3 4
4 5
5 6
6 5
5 4
4 3
3 2
2 1
样例输出 2
1
2
3
4
5
7
11
17
25
30
数据范围与提示
对于所有数据, , ,保证:
- ;
- ;
- 。
详细子任务及附加限制如下表:
子任务编号 | 附加限制 | 分值 |
---|---|---|
1 | N≤50 | 1 |
2 | N≤2×103 | 16 |
3 | 无附加限制 | 83 |
解析
方法:并查集+STL+启发式合并
简单地说就是加入一条边x->y。如果x这个点到y并查集已经有边了,那么这条边就不用加入了。所以维护一个set表示x这个点能到哪些并查集。如果y并查集到x的并查集有边,那么x,y应该merge。
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <set>
#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;
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=1e5+5;
int fa[N],tot[N];
set<int> r[N],c[N];
set<int>::iterator it;
inline int getfa(int x) {
return fa[x]==x ? x : fa[x]=getfa(fa[x]);
}
inline void SWAP(int &a,int &b) {
a=a^b;
b=a^b;
a=a^b;
}
LL ans;
void merge(int x,int y) {
if(x==y) return;
if(r[x].size()+c[x].size()>r[y].size()+c[y].size())
SWAP(x,y);
ans-=1LL*tot[x]*r[x].size()+(LL)tot[y]*r[y].size();
vector<int>vec;
for(it=r[x].begin(); it!=r[x].end(); it++) {
int z=getfa(*it);
if(c[y].count(z))vec.push_back(z);
c[z].erase(x),c[z].insert(y);
r[y].insert(*it);
}
for(it=c[x].begin(); it!=c[x].end(); it++) {
int z=getfa(*it);
if(c[*it].count(y))
vec.push_back(*it);
c[y].insert(*it);
}
tot[y]+=tot[x],fa[x]=y;
ans+=1LL*tot[y]*r[y].size();
for(int i=0; i<vec.size(); i++)
merge(getfa(y),getfa(vec[i]));
}
int main() {
int n,m,x,y;
read(n);
read(m);
for(int i=1; i<=n; i++)
fa[i]=i,tot[i]=1,r[i].insert(i);
while(m--) {
scanf("%d%d",&x,&y);
int fx=getfa(x),fy=getfa(y);
if(fx!=fy) {
if(c[fy].count(fx)) merge(fx,fy);
else if(!r[fy].count(x))
c[fx].insert(fy),r[fy].insert(x),ans+=tot[fy];
}
writeln(ans);
}
return 0;
}
T3 Ruins3
原题
CSDN下载:https://download.csdn.net/download/Ljnoit/12260005
链接
翻译
题目描述
JOI 教授是 IOI 国有名的历史学家。他在研究 IOI 国一个古庙时发现了石柱的遗迹以及一篇古 IOI 国人写的文档。 在文档中,给出了这些石柱的相关描述,具体如下:
- 刚建好时,庙里有 根石柱,编号为 1…2N。对于任意 ,恰好有两根石柱的高度为 k。
- 随后发生了 次地震,损坏了某些石柱,每次损坏将使石柱的高度减一。由于古人类的保护,其他石柱未被损坏,高度保持不变。
- 地震发生时,对于任意 ,古人类只能保护恰好一根高度为 k 的石柱。如果有多根石柱高度相同,根据古人类达成的共识,他们将选择保护编号最大的那一根。也就是说,如果石柱 i 在地震前高度是 hi,古人类会去选择保护这根石柱当且仅当 且任意 满足 。
-
次地震后,只剩下
根石柱了,即只有
根石柱的高度至少为
。
JOI 教授觉得如果他能还原出来这些石柱地震前的高度,他能搞个大新闻。在他更仔细的研究后,发现 次地震后留下来的石柱的编号为 。他想知道原来的高度组合有多少种可能。因为你是 JOI 教授的学徒(pupil),他想让你写个程序计算这个方案数。
你的任务是编写一个程序,给出 次地震后留下来的石柱的编号,计算初始时 根石柱的高度组合种数模 。
输入格式
第一行一个整数 。
接下来一行 个空格分隔的整数 。
输出格式
输出题面描述中所求的方案数模 的值。
样例输入 1
3
3 4 6
样例输出 1
5
样例解释 1
假设初始时石柱的高度为 。因为对于 的各个高度都恰好有 根石柱,因此符合题面描述中的条件。
- 第一次地震时,石柱 被古人类保护。地震后,石柱高度变为 。
- 第二次地震时,石柱 被古人类保护。地震后,石柱高度变为 。
- 第三次地震时,石柱 被古人类保护。地震后,石柱高度变为 。
三次地震后,石柱
留下来了,与输入一致。
除了这个例子,可能的高度组合还有
。
因此,总共有 种高度组合符合输入数据和题面描述的条件。
样例输入 2
1
1
样例输出 2
0
样例解释 2
本输入的唯一可能高度组合为 (1,1)。地震后,石柱高度变为 (0,1)。
因此,没有符合输入数据和题面描述给出的条件的初始高度组合。
样例输入 3
10
5 8 9 13 15 16 17 18 19 20
样例输出 3
147003663
样例解释 3
总共有 111 147 004 440 种符合条件的初始高度组合,将这个数除 109+7 余数为 147 003 663,即输出值。
数据范围与提示
对于 % 的数据,有 。
各子任务详情如下:
子任务编号 | 分值 | 特殊限制 |
---|---|---|
1 | 6 | N≤13 |
2 | 52 | N≤60 |
3 | 42 | 无特殊限制 |
解析
方法:计数dp
从大到小枚举每个点,看看每个点会停在哪儿。
状态转移方程详见代码。
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <set>
#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;
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=605;
const int mod=1e9+7;
const int inv2=(mod+1)>>1;
int fl[N],gl[N],ff[N],gg[N],f[N],g[N],c[N][N];
bool vis[2005];
int main() {
int n,x;
read(n);
for(int i=1; i<=n; i++)
read(x),vis[x]=1;
c[0][0]=1;
for(int i=1; i<=n; i++) {
c[i][0]=1;
for(int j=1; j<=n; j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
fl[0]=gg[0]=1;
for(int i=1; i<=n; i++) {
memcpy(gl,fl,sizeof(fl));
memset(fl,0,sizeof(fl));
for(int j=0; j<=i; j++) if(gl[j]) {
if(j>=1) fl[j-1]=(fl[j-1]+1LL*gl[j]*inv2%mod*inv2)%mod;
fl[j]=(fl[j]+gl[j])%mod;
fl[j+1]=(fl[j+1]+gl[j])%mod;
}
gg[i]=fl[0];
}
int jx=1;
for(int i=1; i<=n; i++)
jx=1LL*jx*i%mod,gg[i]=1LL*gg[i]*jx%mod,ff[i]=gg[i];
ff[0]=gg[0];
for(int i=1; i<=n; i++) {
for(int j=1; j<i; j++)
ff[i]=(ff[i]+1LL*(mod-ff[j])*gg[i-j]%mod*c[i-1][j-1])%mod;
}
f[0]=1;
int nw=0,nn=0;
for(int i=(n<<1); i; i--) {
memcpy(g,f,sizeof(f));
memset(f,0,sizeof(f));
if(vis[i]) {
for(int j=0; j<=nn; j++) if(g[j]) {
f[j]=(f[j]+g[j])%mod;
for(int j1=1; j+j1<=nn+1; j1++) {
f[j+j1]=(f[j+j1]+1LL*g[j]*ff[j1]%mod*c[nn-j][j1-1])%mod;
}
}
nn++;
} else {
for(int j=0; j<=nn; j++)
f[j]=(f[j]+1LL*g[j]*(j-nw))%mod;
nw++;
}
}
writeln(f[n]);
return 0;
}