P7800 [COCI2015-2016#6] PAROVI 方法记录
桔梗花于此开放

[COCI2015-2016#6] PAROVI
题目描述
\(\text{Mirko}\) 和 \(\text{Slavko}\) 在玩一个游戏,先由 \(\text{Mirko}\) 在 \(1\dots N\) 中选出几组互质的数。例如当 \(N=5\) 时,\(\text{Slavko}\) 可以选择 \(\big\{\{1,2\},\{3,4\},\{2,5\},\{3,5\},\cdots\big\}\) 中的几组。
然后轮到 \(\text{Slavko}\)。他需要找到一个 \(x\in \big[2,n\big]\) 使得对于每组 \(\{a,b\}\) 都满足以下两个条件之一:
\(a\),\(b<x\)
\(a\),\(b\ge x\)
例如,如果 \(\text{Mirko}\) 选了 \(\big\{\{1,2\},\{3,4\}\big\}\),那么 \(x\) 可以等于 \(3\)。
如果 \(\text{Slavko}\) 找不到满足条件的 \(x\) 值,则表示 \(\text{Mirko}\) 获得胜利。现在请你求出 \(\text{Mirko}\) 获胜的不同情况的总数,在对 \(10^9\) 取模后告诉他。
输入格式
第一行包含一个整数 \(N\)。
输出格式
第一行输出一个整数,为 \(\text{Mirko}\) 获胜的不同情况的总数对 \(10^9\) 取模后的值。
样例 #1
样例输入 #1
2
样例输出 #1
1
样例 #2
样例输入 #2
3
样例输出 #2
5
样例 #3
样例输入 #3
4
样例输出 #3
21
提示
【样例 1 解释】
\(\text{Slavko}\) 只有一种取法 \(\big\{\{1,2\}\big\}\)。
【样例 2 解释】
\(\text{Slavko}\) 的其中一种取法为 \(\big\{\{1,2\},\{1,3\}\big\}\)。
【数据范围】
对于 \(100\%\) 的数据,\(1\le N\le 20\)。
【题目来源】
题目译自 COCI 2015-2016 CONTEST #6 T4 PAROVI。
本题分值按 COCI 原题设置,满分 \(120\)。
一道终究没能自己想出正解的题
错误的做法
审题
注意到在\(1…N\)中选出几组互质的数,先试想\(N\)个数中选两个数,一共有多少种选法。
以\(N=4\)为例
1,2 1,3 1,4
2,3 2,4
3,4
不难想到,可以用循环嵌套实现
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
//(i,j)就是一组
}
}
接下来实现互质
若两个数的最大公因数为\(1\),则这两个数互质
注意:形如\((1,n)\)的数对也算互质对。
结合欧几里得算法,在遍历数对的循环嵌套中加入一个判断函数,将通过判断的数对存储。
存储方法:由于N最大为20,遍历出来的两个数也很小,所以我们将第一个数乘\(1000\),再加上第二个数,就可以用一个元素存下这个二元组。
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
bool judge(int a,int b)
{
if(a>b) swap(a,b);
if(a==1) return 1;
for(int i=2;i<=a;i++)
{
if(gcd(a,b)!=1) return 0;
}
return 1;
}
//
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(!judge(i,j)) continue;
choose[++cnt]=i*1000+j;
}
}
这样我们可以得到每一组互质对的情况。而Mlavko可以在这些互质对中选取若干组,比如一共有\(3\)组(注意是组,和前面的互质的数区分。一个互质对是一组)
那么选取方案如下:
1 12 123 13 2 23 3
可以用全组合获取。
#include<iostream>
#include<cstdio>
using namespace std;
int n,pd[1000],used[1000];
void dfs(int f,int s)
{
for(int i=s;i<=n;i++)
{
if(!pd[i])
{
pd[i]=1;
used[f]=i;
for(int j=1;j<=f;j++) cout<<used[j];
cout<<" ";
dfs(f+1,i);
pd[i]=0;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1,1);
return 0;
}
//input 3
//output 1 12 123 13 2 23 3
结合以上两项,我们可以得到所有可能选取的互质对,以及互质对中的两个元素。
如当\(N=3\)时:
(靠得近的为一组,输出的数为互质对中的数,括号中的为组编号)
12(1)
12(1) 13(2)
12(1) 13(2) 23(3)
12(1) 23(3)
13(2)
13(2) 23(3)
23(3)
解题重点:确定什么情况下,Slavko能找到x满足要求
根据题意,有以下\(3\)种情况:
1.Mirko选的所有互质对的所有元素的最小值大于等于2——这时\(x=2\)Mirko就会输;
用minn记录最小值即可
2.Mirko选的所有互质对的所有元素的最大值小于n——这时\(x=n\)Mirko就会输;
用maxx记录最大值即可
3.Mirko选择\((a,b)(c,d)\)且满足\(a<b<c<d\)这样的,当\(x=c\)时Mirko就会输。
开一个\(int\)数组,每收到一组\((i,j)\),就从i到j数组中的每一个元素值+1。处理完毕后遍历该数组,若有元素(1除外)的值大于1,则Mirko会输
特别地,若Mirko选择了\((1,n)\),则Mirko必胜。但由于大部分的情况都被上述三种考虑过了,只有一种情况:即Mirko只选了\((1,n)\)这一组的情况未被考虑,所以可以单独判断,也可以直接给答案+1
void sat(int x)
{
int a,b;
bool p[1000];
for(int i=1;i<=n+1;i++) p[i]=1;
bool flag=1;
a=choose[x]/1000;//将两个元素还原出来
b=choose[x]-a*1000;
if(a==1&&b==n)
{
one_n=1;//这里特判了{1,n}
}
cout<<a<<b<<" ";
minn=min(a,minn);
maxx=max(b,maxx);
for(int i=a;i<=b;i++) reg[i]++;//累加数组
}
综上所述,总代码如下
点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int mod=1e9;
int n,cnt,ans;
int choose[1000],used[1000],reg[1000];
bool pd[1000],one_n=0;
int minn=1000,maxx=-1000;
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
bool judge(int a,int b)
{
if(a>b) swap(a,b);
if(a==1) return 1;
for(int i=2;i<=a;i++)
{
if(gcd(a,b)!=1) return 0;
}
return 1;
}
void sat(int x)
{
int a,b;
bool p[1000];
for(int i=1;i<=n+1;i++) p[i]=1;
bool flag=1;
a=choose[x]/1000;
b=choose[x]-a*1000;
if(a==1&&b==n)
{
one_n=1;
}
cout<<a<<b<<" ";
minn=min(a,minn);
maxx=max(b,maxx);
for(int i=a;i<=b;i++) reg[i]++;
}
void dfs(int f,int s)
{
for(int i=s;i<=cnt;i++)
{
if(!pd[i])
{
pd[i]=1;
used[f]=i;
bool flag=1;
minn=1000,maxx=-1000;
memset(reg,0,sizeof(reg));
one_n=0;
for(int j=1;j<=f;j++)
{
sat(used[j]);
}
puts("");
for(int j=2;j<=n;j++)
{
if(reg[j]>1)
{
flag=0;
break;
}
}
ans++;
if(minn>=2||maxx<n||flag==1)
{
ans--;
if(one_n==1) ans++;
}
else cout<<" yes"<<endl;
dfs(f+1,i);
pd[i]=0;
}
}
}
int main()
{
//freopen("parovi.in","r",stdin);
//freopen("parovi.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(!judge(i,j)) continue;
choose[++cnt]=i*1000+j;
}
}
dfs(1,1);
printf("%d",ans);
return 0;
}
这是错误的做法
当\(n=2,3,4\)时输出均正确,但当\(n=5\)时答案错误,且这个程序复杂度很极品。我改了又改只为把\(n=5\)的情况整对,但还是失败了,所以这个程序是个错误的暴力。
下面是正确的做法
合适的算法:线性DP
如果执意要用搜索的同学可以看看这篇
虽然我的做法是错的,但是部分分析是可以利用的
一、预处理互质对
二、Mirko会失败的情况:
1.Mirko选的所有互质对的所有元素的最小值大于等于2——这时\(x=2\)Mirko就会输;
2.Mirko选的所有互质对的所有元素的最大值小于n——这时\(x=n\)Mirko就会输;
3.Mirko选择\((a,b)(c,d)\)且满足\(a<b<c<d\)这样的,当\(x=c\)时Mirko就会输。
并且由第三种情况我们不难推出标算的思想:把互质对看做一条\(a->b\)的线段,问题就转化为区间覆盖问题
解题步骤
1.设计\(dp\):\(dp[i][j]\)为选到第\(i\)个互质对,即第\(i\)条线段时,覆盖区间\(1~j\)的方案数,若cnt为互质对的总数,则答案为\(dp[cnt][n]\);
2.将所有线段(互质对)按照右端点大小从小到大排序;
3.注意初始化:\(dp[0][1]=1\)。
4.推导\(dp\)方程:设\(l_i r_i\)分别为第\(i\)条线段的左右端点。
\(dp[i][j]=dp[i][j]+dp[i-1][j]\)
\(if(l_i<=j) dp[i][r_i]=dp[i][r_i]+dp[i-1][j]\)
\(if(l_i>j) dp[i][j]=dp[i][j]+dp[i-1][j]\)
以下为AC代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1500;
const int mod=1e9;
int n,cnt;
struct P
{
int x,y;
}a[N];
bool cmp(P a,P b)
{
return a.y<b.y;
}
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
void Dp()
{
ll dp[N][25];
dp[0][1]=1;
for(int i=1;i<=cnt;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
if(a[i].x<=j) dp[i][a[i].y]=(dp[i][a[i].y]+dp[i-1][j])%mod;
else dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
}
}
printf("%lld\n",dp[cnt][n]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
int Gcd=gcd(i,j);
if(Gcd==1) a[++cnt]={i,j};
}
}
sort(a+1,a+cnt+1,cmp);
Dp();
return 0;
}
P7800 [COCI2015-2016#6] PAROVI 方法记录的更多相关文章
- EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态
本文目录 查看实体当前.原始和数据库值:DbEntityEntry 查看实体的某个属性值:GetValue<TValue>方法 拷贝DbPropertyValues到实体:ToObject ...
- 64位 SQL Server2008链接访问Oracle 过程汇总解决方法记录
64位 SQL Server2008链接访问Oracle 过程汇总解决方法记录 经过几天不停的网上找资料,实验,终于联通了. 环境:系统:win 2008 ,SqlServer2008 R2, 连接O ...
- js实用方法记录-js动态加载css、js脚本文件
js实用方法记录-动态加载css/js 附送一个加载iframe,h5打开app代码 1. 动态加载js文件到head标签并执行回调 方法调用:dynamicLoadJs('http://www.yi ...
- js实用方法记录-简单cookie操作
js实用方法记录-简单cookie操作 设置cookie:setCookie(名称,值,保存时间,保存域); 获取cookie:setCookie(名称); 移除cookie:setCookie(名称 ...
- js实用方法记录-指不定哪天就会用到的js方法
js实用方法记录-指不定哪天就会用到的js方法 常用或者不常用都有 判断是否在微信浏览器中 测试代码:isWeiXin()==false /** * 是否在微信中 */ function isWeix ...
- Java给各个方法记录执行时间
Java给各个方法记录执行时间 long startTime = System.currentTimeMillis();...//要测试时间的方法LoggerFactory.getLogger(Bas ...
- make menuconfig error 解决方法记录
新建的一个虚拟机,发现make menuconfig 后会出错,查了一下是缺少一些库. 这个错误已经错了两次了,我不希望第三次出现了还想不起来,所以特此记录. # 错误信息: make[2]: *** ...
- 简单一键CENTOS6 安装PPTP VPN方法记录
申明:我们使用PPTP VPN仅仅只能用在查阅资料等正规渠道,不要用在不良用途上.方法收集于网上,这里我用在搬瓦工VPS(VPS方案直达),采用的是CENTOS6 64位系统.我们需要预先将VPS服务 ...
- ASP.NET页面优化性能提升方法记录
今天与大家分享:一种优化页面执行速度的方法.采用这个方法,可以使用页面的执行速度获得[8倍]的提升效果. 为了让您对优化的效果有个直观的了解,我准备了下面的测试结果截图: 测试环境:1. Window ...
随机推荐
- zabbix监控添加学习笔记
在实际生产环境中,除了CPU.内存等一些系统信息可以挂载zabbix的自带模板Template OS Linux:但是一些公司开发的定制服务需要自己写模板或者监控项去监控: 一.监控公司的java服务 ...
- 获取某个html元素相对于视窗的位置集合
getBoundingClientRect() getBoundingClientRect()获取元素位置,这个方法没有参数 getBoundingClientRect()用于获得页面中某个元素的左, ...
- 编译式安装MYSQL
安装所需组件 实际上只需要这四个配置即可 升级boot开始 升级结束 重新执行cmake make && make install 启动mysql 发现启动失败 #####原因:这是由 ...
- 如何基于WPF写一款数据库文档管理工具(二)
系列目录 基于WPF重复造轮子,写一款数据库文档管理工具(一) 本篇重点 上次发表了基于WPF重复造轮子,写一款数据库文档管理工具(一) 得到不少人支持,文章一度上到了博客园推荐表首页,看来大家对这个 ...
- 万答#3,MGR最佳配置参考,PFS里的监测指标要全开吗,mysqld进程占用内存过高怎么排查
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 问题1,有推荐的MGR运行最佳配置参考吗 在「3306π」社区广州站5月22日的分享会上,万里数据库CTO娄帅给出了他建议 ...
- JAVA语言基础组成(2)
函数 函数的定义 1.什么是函数? 函数就是定义在类中的具有特定功能的一段独立小程序.函数也称为方法. 2.函数的格式: 修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,.. ...
- Java开发学习(二十四)----SpringMVC设置请求映射路径
一.环境准备 创建一个Web的Maven项目 参考Java开发学习(二十三)----SpringMVC入门案例.工作流程解析及设置bean加载控制中环境准备 pom.xml添加Spring依赖 < ...
- (防坑)Alphafold 非docker 安装指南
本指南适用于Linux系统.Alphafold官方也强调尽量使用Linux系统!官方提供了docker版安装步骤. Alphafold简介: 强大的蛋白质结构预测. 开源地址:https://gith ...
- YC-Framework版本更新:V1.0.9
分布式微服务框架:YC-Framework版本更新V1.0.9!!! 本文主要内容: 1.V1.0.9版本更新主要内容 2.YC-Framework新的征程 一.V1.0.9版本更新主要内容 (1)接 ...
- ARC116 A Odd vs Even (质因数分解,结论)
题面 有 T T T 组数据,每次给出一个数 N N N ,问 N N N 的所有因数(包括 1 1 1 和 N N N)中奇因数个数和偶因数个数的关系(">"," ...