USACO 5.5 章节
Picture
题目大意
IOI 1998
求n (<=5000)个矩形 覆盖的图形 的周长(包括洞), 坐标范围[-10000,10000]
题解
一眼离散化+2维线段树,但仔细一想 空间不太够,时间勉强接受
然后目测可能1维线段树+扫描线了?
然后 竟然 裸的扫描线可以过,如下面代码
总数量级上来讲,输入O(n)
,排序O(n log n)
,扫描过程O(sum(len周长))
约5000*20000*4
的上限[ 不过USACO给过了,
所以还是线段树好?
从实现来讲,把矩形拆分成x和y方向,靠计算每个块的累计层数 来判断边界
#include <bits/stdc++.h>
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
using namespace std;
const string filename = "picture";
void usefile(){
freopen((filename+".in").c_str(),"r",stdin);
freopen((filename+".out").c_str(),"w",stdout);
}
int N,ans=0;
// OX向和OY向的 线段分离处理,互不影响
tuple<int,bool,int,int> Lx[10010],Ly[10010]; // {位置idx坐标,结束边bool?,st->end}
int level[20010];
void Scan(tuple<int,bool,int,int> *L) {
sort(L,L+N);
rep(i,0,20001)
level[i]=0;
rep(i,0,N){
rep(j,get<2>(L[i]),get<3>(L[i])){
if (!get<1>(L[i])){
ans += level[j+10000]==0;
level[j+10000]++;
} else {
level[j+10000]--;
ans += level[j+10000]==0;
}
}
}
}
int main(){
usefile();
scanf("%d",&N);
rep(i,0,N){
int x1,x2,y1,y2;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
// OX 方向边
Lx[i*2] = {y1,false,x1,x2};
Lx[i*2+1] = {y2,true,x1,x2};
// OY 方向边
Ly[i*2] = {x1,false,y1,y2};
Ly[i*2+1] = {x2,true,y1,y2};
}
N*=2;
Scan(Lx);
Scan(Ly);
printf("%d\n",ans);
return 0;
}
Hidden Password
ACM South Eastern Europe -- 2003
题目大意
字符串L(长度<=100’000
)
求 以该字符串,平移后的, 所有字符串中 字典序最小的字符串的首字母在原字符串中的下标
如cba
所有字符串 acb bac cab, (排序后),第一个字符串首字母对应原来字符串位置为2 (从0计数)
题解
枚举!
这样 我们首先找到最小字母 扫一遍O(n)
然后找到这些开始的坐标
逐步增加长度
那么我们由递归关系,保证每次增加长度后,当前还剩余的坐标只有最小的留下,
因此当增加长度到l
时,我们 的维持的起始坐标是 整个字符串里,长度从1
到l
都是字典序最小的
那么我们有两种长度改变方式
- +1
假设所有点按照长度扩充 都不属于当前维护的点,那么,长度加1,保留,增加字符最小的点
例子 abcabdzzzabc
初始所有a
的坐标[0,3,9]
,长度1
扩充,扩充目标分别为[1,4,10]
,都不是当前维护的点([0,3,9]
)
所以比较元素,全为b,长度=1+1=2
接下来,扩充目标为[2,5,11]
,也都不是维护的点
比较字符,两个abc
,一个abd
,所以维护的点变为[0,9]
,长度变为=2+1=3
再扩充,扩充目标为[3,0]
,注意到0
是我们当前维护的[0,9]
中的元素,所以不采取+1
的方案
- 倍增
假设字符aabbabbb,
那么在找完最小字符后,起始坐标还剩下[0,1,4]
,一旦发现任意一个扩充的下一步([1,2,5]
) 是一个维持的点,那么长度翻倍,后一个点删除,在这种情况下,扩充的位置不是最小坐标的点直接移除。
因为我们维持的点 == 从1到该长度下,每个长度 都是字典序最小的,所以没有在维护中的点,都是非字典序最小的,所以 可以倍增
删除右边的点是因为 扩充右边的维护点的字典序一定大于等于 左边的点,等于的情况由判环处理
如上在一次扩充后发现0
的下一个扩充是1
,而1
是我们维持着的点,所以长度=1*2
,1
点删除,4
扩充是5
,那么5
没有被维持,所以4
点也被删除,综上最后剩下0
以上描述存在的问题:起始点是哪个点?
假设字符串 aaazzaaza
,
显然在初始操作后 需要维护的点有[0,1,2,5,6,8]
注意到,如果从左向右来处理,按照上面的算法会 变成[0,6,8???]
,而实际 从环的性质来看,期望的应该是得到[1,6,8]
,也就是8
位置的看做[8,0,1,2]
这一段的起始点。
这里加一个父节点的查找,找到环意义上该点所对应的最左的点即可,在下方函数看的话就是circle
,
同时,circle
这里如果发现,整个保存的点构成了环,那么也就是 这些点仅对于环上字典序的等价了,根据题目期望这种情况下最小index,就取出即是答案
空间复杂度,emmmm没啥好说,看变量即可,维持在O(n)
时间复杂度,
每一次倍增会让点数至少除以2,因为一个点要留下来,那么首先它的扩展点要在原来的维护里,并且下一次维护需要消失,所以每次要留一个点,就一定要删一个点,还有的点不会被留下,所以留下的一定小于等于上一次的一半
O(n+n/2+n/4) = O(2n) = O(n)
考虑对任意长度,都是执行+1,那么每次能执行+1的限度为
sum(n*(1+1/2+...1/n))
众所周知这是一个无穷级数,所以时间复杂度无穷大
大自也是O(12n)=O(n)
的复杂度,
下面就是实际上,是这两种穿插 ,那么一定小于等于O(2n+12n)=O(n)
, (数学的习惯懒得分类讨论不等的情况 能用就行,所以留一个等于)
综上 时间空间均满足
char s[100010];
int sz=0;
bool pos[100010];
vector<int>p;
vector<int>q;
int L;
bool circle(int idx,int len,int &newidx){
newidx = (idx+L-len)%L;
while(newidx != idx){
if(!pos[newidx]){
(newidx+=len)%=L;
return false;
}else{
newidx = (newidx+L-len)%L;
}
}
while(newidx - L > 0){
newidx -= L;
}
printf("%d\n",newidx);
return true;
}
int main(){
usefile();
// 同步增长,冲突取前,倍增 其余删除(因为保证最小)
scanf("%d",&L);
while(sz<L){
scanf("%s",s+sz);
sz+=strlen(s+sz);
}
char minch = s[0];
rep(i,1,L){
minch = min(minch,s[i]);
}
rep(i,0,L){
if(s[i] == minch){
p.push_back(i);
pos[i]=true;
}
}
int l = 1;
while(p.size() > 1){
int state = 0; // 0 for +1, 1 for *2
minch = s[(p[0]+l)%L];
for(auto idx : p){
if(pos[(idx+l)%L] == true){
state = 1;
break;
}
minch = min(minch,s[(idx+l)%L]);
}
if(state == 0){
q.clear();
for(auto idx:p){
if(!pos[idx])continue;
if(s[(idx+l)%L] == minch){
q.push_back(idx);
}else{
pos[idx]=false;
}
}
p=q;
l++;
}else{
q.clear();
int startidx ;
int ret = circle(p[0],l,startidx);
if(ret){
return 0;
}
int pidx = 0;
for(pidx=0;pidx<p.size();pidx++){
if(p[pidx] == startidx){
break;
}
}
rep(i,pidx,p.size()){
int idx = p[i];
if(!pos[idx])continue;
if(pos[(idx+l)%L]){
q.push_back(idx);
pos[(idx+l)%L] = false;
}else{
pos[idx]=false;
}
}
rep(i,0,pidx){
int idx = p[i];
if(!pos[idx])continue;
if(pos[(idx+l)%L]){
q.push_back(idx);
pos[(idx+l)%L] = false;
}else{
pos[idx]=false;
}
}
p=q;
l*=2;
}
}
printf("%d\n",p[0]);
return 0;
}
Twofive
题目大意
IOI 2001
A到Y
构成的排列,满足 把这25
个字母排成 5*5
矩阵后 每行每列,单调递增,则为合法的
所有合法的排列,按照字典序 排序
请编写 字典序序号 到字符串 和 字符串反向转换为字典序序号的程序
尝试思路
看数据量,我自己也估计不到实际大小于是先写了一个打表,(上界 是25!
但是有限制所以不知道是否会降低
#include <bits/stdc++.h>
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
using namespace std;
const string filename = "twofive";
void usefile(){
freopen((filename+".in").c_str(),"r",stdin);
freopen((filename+".out").c_str(),"w",stdout);
}
int chars[30];
int vis[30];
int cnt = 0;
void print(){
cout<<endl;
rep(i,0,5){
rep(j,0,5){
cout<<char(chars[i*5+j]+'a')<<" ";
}
cout<<endl;
}
}
void gen(int idx){
if(idx%5==0){
rep(i,0,25){
if(vis[i] == 0){
vis[i]=1;
chars[idx] = i;
gen(idx+1);
vis[i]=0;
return ;
}
}
}
if(idx == 24){
cnt++;
chars[24]=24;
print();
return ;
}
int sti = chars[idx-1];
if(idx>5){
sti=min(sti,chars[idx-5]);
}
rep(i,sti+1,26-(5-idx%5)*(5-idx/5)){
if(vis[i])continue;
vis[i]=1;
chars[idx] = i;
gen(idx+1);
vis[i]=0;
}
}
int main(){
// usefile();
gen(0);
cout<<cnt<<endl;
return 0;
}
跑了一下大概
7951237
a b c d e
f g h l n
i m k q w
j p o r u
s t v x y
以上的改动才到i
,说明 数量级上,不可能期望打表了
接下来,注意到,如果我们有方法从数字序号转换到 对应的单词,那么 可以2分法 找到对应的单词
同理,如果我们找到 单词映射到序号的方法,那么2分(如果可以,因为这里二分单词似乎没那么好做)也能反过来找数字,所以分析上,任何一个问题 都可以解决对应的一个
还有个思路是,简单的排序,计算被删掉的个数,那么序列= 总排序-被删掉比它小的个数
题解
记忆化+dp
我们如果把数从小到大填入
那么显然,新插入的数在已经插入的数的行末,也在已经插入的数的列末,如
a b e
c d f
g
h
i
j
可插入的位置 为g右侧
或e右侧
所以 我们有dp
dp[i0][i1][i2][i3][i4]
表示 第0行i0
个,第1行i1
个数...第i4
行个数的情况下,剩余未填部分的期望个数
不考虑具体,仅考虑题目基本限制的情况下, 满足 ij >= i(j+1)
,因为我们按照顺序放数字,所以上面的行的个数不小于下一行
有转移方程
dp[i0][i1][i2][i3][i4] = dp[i0-1][...]+dp[...][i1-1][...]+dp[...][i2-1][...]+dp[...][i3-1][...]+dp[...][i4-1]
其中 如果在-1
时不满足 行之间个数的大小关系,那么 对应的dp直接为0
综上,我们有了在没有 具体求值下,能计算所有满足题目限制下的dp,时间复杂度 = O(空间*状态转移)=O(6^5*5)
,空间O(6^5)
求解
接下来是如何进行转换的问题求
因为所求的idx为实际的 合法twofive的字典序
那么我们可以按位逼近,//延伸阅读 BROP的按位枚举攻击方法
假设我们求的是
ADEFGBHIJKC...Y
, 那么 它 的字典序 = AB...的所有
+AC...的所有
+...
简单的说,如果一个前缀小于 要求的等长前缀,那么加上该前缀的所有个数,
如果该前缀等于要求的值的等长前缀,那么前缀长度+1
外层for前缀长度,中间for字母, 时间复杂度小于 O(25*25)
以上我们可以 从 字符串转化为index
相反
同样用逼近的方法,可以O(25*25)
时间复杂度内 index转化为 字符串
#include <bits/stdc++.h>
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
using namespace std;
const string filename = "twofive";
void usefile(){
freopen((filename+".in").c_str(),"r",stdin);
freopen((filename+".out").c_str(),"w",stdout);
}
char s[100]; // 按位逼近的
char str[100]; // str2index
int dp[6][6][6][6][6];
int dfs(int a=0, int b=0, int c=0, int d=0, int e=0, char ch='A') {
if(ch > 'Y') return 1;
int &ret = dp[a][b][c][d][e];
if(ret) return ret;
// 每一行 一定小于等于上一行
int w = 5;
int *v[6]={&w,&a,&b,&c,&d,&e};
rep(i,1,6){
// 未填过 和 已经填过(按照 字母顺序扩展)
int idx = *v[i]+(i-1)*5;
if(*v[i] < *v[i-1] && (s[idx] == 0 || s[idx] == ch)){
(*v[i])++;
ret+=dfs(a,b,c,d,e,ch+1);
(*v[i])--;
}
}
return ret;
}
void index2word(){
int n;
scanf("%d",&n);
rep(i,0,25){
for(s[i] = 'A';; s[i]++) { // 按位逼近 时间复杂度25×25
memset(dp, 0,sizeof(dp));
int ret = dfs();
// cout<<i<<" = "<<s[i]<<"\tret = "<<ret<<endl;
if(ret >= n) break;
n -= ret;
}
}
printf("%s\n", s);
}
void word2index(){
scanf("%s", str);
int ans = 1;
rep(i, 0, 25) {
for(s[i] = 'A'; s[i] < str[i]; s[i]++) {
memset(dp, 0,sizeof(dp));
ans += dfs();
}
}
printf("%d\n", ans);
}
int main(){
usefile();
char c;
cin >> c;
if(c == 'N') { // index 2 word
index2word();
} else if(c == 'W') { // word 2 index
word2index();
}
return 0;
}
上面实现中 dp过程是按照字母顺序填写,由ch保证,所以在最外层枚举dp的时候,就直接从A 到Y 了
USACO 5.5 章节的更多相关文章
- USACO 6.4 章节
The Primes 题目大意 5*5矩阵,给定左上角 要所有行,列,从左向右看对角线为质数,没有前导零,且这些质数数位和相等(题目给和) 按字典序输出所有方案... 题解 看上去就是个 无脑暴搜 题 ...
- USACO 6.3 章节 你对搜索和剪枝一无所知QAQ
emmm........很久很久以前 把6.2过了 所以emmmmmm 直接跳过 ,从6.1到6.3吧 Fence Rails 题目大意 N<=50个数A1,A2... 1023个数,每个数数值 ...
- USACO 5.4 章节
Canada Tour 题目大意 双向连通图,点从左向右排列, 你需要先从最左的点到最右的点,(过程中只能从左向右走) 然后再从最右的点返回最左的点,(过程中只能从右向左走) 过程中除了最左的点,其它 ...
- USACO 5.3 章节
相关讲解可在USACO上看原文,也可以搜索nocow找到翻译的! (nocow上有些微翻译是有问题的,如果想看nocow翻译的建议也对着英文看) 以下记录以下 自己之前未掌握的一些要点,以及按自己的括 ...
- USACO 6.5 章节 世界上本没有龙 屠龙的人多了也便有了
All Latin Squares 题目大意 n x n矩阵(n=2->7) 第一行1 2 3 4 5 ..N 每行每列,1-N各出现一次,求总方案数 题解 n最大为7 显然打表 写了个先数值后 ...
- USACO 6.1 章节
Postal Vans 题目大意 4*n的网格,要经过所有点的有向有环,不重复经过点的路径总数 n<=1000 题解 显然 插头dp 以4为切面 问题是,会发现 超精度 解决呢要么实现高精度,要 ...
- (Step1-500题)UVaOJ+算法竞赛入门经典+挑战编程+USACO
http://www.cnblogs.com/sxiszero/p/3618737.html 下面给出的题目共计560道,去掉重复的也有近500题,作为ACMer Training Step1,用1年 ...
- 算法竞赛入门经典+挑战编程+USACO
下面给出的题目共计560道,去掉重复的也有近500题,作为ACMer Training Step1,用1年到1年半年时间完成.打牢基础,厚积薄发. 一.UVaOJ http://uva.onlinej ...
- USACO . Your Ride Is Here
Your Ride Is Here It is a well-known fact that behind every good comet is a UFO. These UFOs often co ...
随机推荐
- JS的两种函数声明方式的区别
---恢复内容开始--- js中常见的两种函数声明方式如下: // 函数表达式 var f = function() { console.log(1); } // 直接声明 function f () ...
- android概念-android学习第二天
一:1G到4G(generation) 1G 大哥大 -- 电话 2G 小灵通 gsm标准 发短信 wap.baidu.com -- 电话和短信 3G 沃 www.baidu.com 7.2M/s - ...
- RMQ(连续相同最大值)
http://poj.org/problem?id=3368 Frequent values Time Limit: 2000MS Memory Limit: 65536K Total Submi ...
- LTP安装方法
git clone https://github.com/linux-test-project/ltp.git apt-get install automake make autotools ./co ...
- JVM中类加载器的父委托机制
类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap) 扩展类加载器(Extension) 系统类加载器 ...
- JavaScript中的柯里化
转载自:https://www.cnblogs.com/zztt/p/4142891.html 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Ha ...
- Windows下svn使用教程
SVN简介: 为什么要使用SVN? 程序员在编写程序的过程中,每个程序员都会生成很多不同的版本,这就需要程序员有效的管理代码,在需要的时候可以迅速,准确取出相应的版本. Subversion是什么? ...
- ubuntu14.0 服务器webmin安装
添加相关的源“: sudo vim /et/apt/source.list 在文件后添加: deb http://download.webmin.com/download/repository sar ...
- ltp-ddt wdt_test
# @name Watchdog Timer getsupport,settimeout,getstatus,keepalive ioctl and write test# @desc Watchdo ...
- Spring MVC 跳转失败,但配置正确填坑
1:正确写法 @RequestMapping("{type_key}.html") public String geren(Model model, @PathVariable S ...