[Codeforces #172] Tutorial
Link:
A:
一眼看上去分两类就可以了
1、每个矩形只有两条边相交,重合的形状为菱形
2、每个矩形四条边都有相交
对于情况1答案为$h*h/sin(a)$
对于情况2可以列出一个二元一次方程组,手动解一下就好了
不过计算几何确实容易写挂啊……
有几个注意点:
1、对于情况1$h$指的是较短的边,不符合时要交换$w,h$
以后处理长方形时还是要注意,不能只看样例啊
2、在判断情况1,2的临界点时用$sin$来判断,因为此时$a$已经全转为锐角了
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
#define PI M_PI
typedef long long ll;
typedef pair<int,int> P;
double w,h,a,x,y,res; int main()
{
scanf("%lf%lf%lf",&w,&h,&a);
if(!a||a>=) return printf("%.9lf",w*h),;
if(a>) a=-a;
if(h>w) swap(w,h);//情况1要求h是短边
a=a*PI/;
if(sin(a)>=sin(*atan2(w,h)))//由于角度已改变,用sin判断
res=h*h/sin(a);
else
y=(h*(cos(a)+)-w*sin(a))/((cos(a)+)*(cos(a)+)-sin(a)*sin(a)),
x=(h-y*(cos(a)+))/sin(a),res=w*h-(x*x+y*y)*sin(a)*cos(a);
printf("%.9lf",res);
return ;
}
Problem A
其它方法:
1、现场大部分人都是暴力半平面交做的……
如果怕少考虑特解这样确实能大大降低写挂概率
2、对于情况2也可以不用解方程
其中三角形的直角边和长/宽一半的差是能直接算的,这样好像更简单一些
B:
一开始全在想异或性质的应用……
但后来发现此题仅和如何枚举所有区间的最大+次大值有关
其实就是要想到一种能滤去所有“无用区间”的方式
考虑无用区间产生的原因就是往最大/次大值两侧扩展了更小的值
如果考虑当前值所有可行的次大值,则其一定是从右向左递增的
这样用一个单调栈维护,每次新加进一个数和 比其小的数+第一个比其大的数 的异或值更新答案
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=1e5+;
int n,x,st[MAXN],top,res; int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d",&x);
while(top&&x>st[top])
res=max(res,x^st[top--]);
if(top) res=max(res,x^st[top]);
st[++top]=x;
}
printf("%d",res);
return ;
}
Problem B
C:
感觉自己还是有期望恐惧症啊……
此题实为一个结论题,结论为$\sum \frac{1}{dep[i]}$
这其实是利用了期望是线性的这一性质,即$E(x+y)=E(x)+E(y)$
有了该性质就可以单独考虑每个点被直接删除的期望,再相加即可
一个点最终被删除当且仅当其到根的路径上的任意一点被删除
因此其直接删除该点的期望为$\frac{1}{dep[i]}$,最后再求和
#include <bits/stdc++.h> using namespace std;
const int MAXN=1e5+;
vector<int> G[MAXN];
int n,x,y;double res; void dfs(int x,int anc,int dep)
{
res+=1.0/dep;
for(int i=;i<G[x].size();i++)
if(G[x][i]!=anc) dfs(G[x][i],x,dep+);
} int main()
{
scanf("%d",&n);
for(int i=;i<n;i++)
scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
dfs(,,);
printf("%.6lf",res);
return ;
}
Problem C
D:
此题将一类经典问题加上了修改和动态查询
该经典问题是指:在$n$个数中取$k$段,使得最终和最大
先考虑静态问题,发现是可以直接$dp$的
用$dp[i][j][0/1]$表示前$i$个数中取$j$段的最大和,同时1表示最后一个数必须取
这样的复杂度为$O(n*k)$
不过这个复杂度是可以用fhq提出的增量法优化的:
每次取当前和最大的一段,同时将该段中的数全部取反。重复$k$次
其中的取反操作其实就是提供了“反悔”机制,如果后面有一段跨过了取反的段就说明这一段不再取了
同时取反后的数依然满足求最大和的目的,毕竟加上了最大取反的数就是少减去了最大的数
对于上面两个操作明显可以用线段树来实现,复杂度是$O(k*log(n))$
这个方法同样可以用费用流来理解
如果对原问题建图将是$<S,i,1,0><i,T,1,0><i,i+1,1,a[i]>$
发现上面的增量法其实就是优化了跑该网络流的过程
接下来用上面的方法来解决动态问题
对于朴素的$dp$方法,对每个点记录$f[k][0/1/2/3]$
表示在该点表示的区间中取$k$段的最大值,限制分别为头必取/尾必取/头尾都取/无限制
但这样将两个区间合并需要$O(k^2)$,总复杂度为$O(q*k^2*log(n))$,想过要稍微卡卡常
但是增量法可以将总复杂度降为$O(q*k*log(n))$,原因在于线段树中每个点都只要保留取1段的最大值
每次的区间合并是$O(1)$的,不过对于每次查询要进行$k$次求最大和、区间取反、撤销
对于每个点记录$mx,lmx,rmx$分别表示取一段的最大和、最大前缀和、最大后缀和
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
#define mid ((l+r)>>1)
#define ls (k<<1)
#define rs (k<<1|1)
#define lc ls,l,mid
#define rc rs,mid+1,r
typedef long long ll;
typedef pair<int,int> P;
typedef double db;
const int MAXN=1e5+;
int top,L[MAXN],R[MAXN];
int n,m,dat[MAXN],rev[MAXN<<],op,l,r,k,res;
struct SGT
{
int l,r,lm,rm;
int lmx,rmx,mx,sum;
void mdy(int val,int pos)
{
sum=val;
l=r=lm=rm=pos;
lmx=rmx=mx=(val>?val:);
}
void upd(SGT a,SGT b)
{
sum=a.sum+b.sum;
lmx=a.lmx;lm=a.lm;rmx=b.rmx;rm=b.rm;
if(a.mx>b.mx) mx=a.mx,l=a.l,r=a.r;
else mx=b.mx,l=b.l,r=b.r; if(a.rmx+b.lmx>mx) mx=a.rmx+b.lmx,l=a.rm,r=b.lm;
if(a.sum+b.lmx>lmx) lmx=a.sum+b.lmx,lm=b.lm;
if(a.rmx+b.sum>rmx) rmx=a.rmx+b.sum,rm=a.rm;
}
}seg[MAXN<<][],ret,tmp; void Pushup(int k)
{
for(int i=;i<;i++)
seg[k][i].upd(seg[ls][i^rev[ls]],seg[rs][i^rev[rs]]);
}
void Update(int pos,int val,int k,int l,int r)
{
if(l==r)
{
seg[k][].mdy(val,pos);
seg[k][].mdy(-val,pos);
return;
}
if(pos<=mid) Update(pos,val,lc);
else Update(pos,val,rc);
Pushup(k);
}
void Modify(int a,int b,int k,int l,int r)
{
if(a<=l&&r<=b){rev[k]^=;return;}
if(a<=mid) Modify(a,b,lc);
if(b>mid) Modify(a,b,rc);
Pushup(k);
}
void Query(int a,int b,int k,int l,int r,int f)
{
f^=rev[k];//永久化标记
if(a<=l&&r<=b)
{
if(l==a) ret=seg[k][f];
else tmp.upd(ret,seg[k][f]),ret=tmp;
return;
}
if(a<=mid) Query(a,b,lc,f);
if(b>mid) Query(a,b,rc,f);
} int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
scanf("%d",&dat[i]),Update(i,dat[i],,,n);
scanf("%d",&m);
while(m--)
{
scanf("%d%d%d",&op,&l,&r);
if(op==) dat[l]=r,Update(l,r,,,n);
else
{
scanf("%d",&k);
top=;res=;
while(top<k)
{
Query(l,r,,,n,);
if(ret.mx<=) break;
res+=ret.mx;L[++top]=ret.l;R[top]=ret.r;
Modify(L[top],R[top],,,n);
}
printf("%d\n",res);//对更改进行撤销
while(top) Modify(L[top],R[top],,,n),top--;
}
}
return ;
}
Problem D
此题有一些技巧是要注意的
1、翻转操作是可以用永久化标记的!
对每个点同时记录两种状态下的值,查询时用一个值记录当前实际是哪种状态即可
只要修改操作和值无关一般都可以不用$pushdown$,尽量用永久化标记
2、线段树对多段问题的更新上传
对于多段问题每个节点都要记录两个边界是否选取来确定合并之后的段数,算是个套路吧
E:
$Seter$出的神题……
普通的$dp$非常好想,用$dp[i][j]$表示第$i$个数选$j$的代价,$f[i][j]$表示$min(dp[i-1][j-k]),k\in[a,b]$
那么$dp[i][j]=2*(j-dat[i])+f[i][j]$
但其时间/空间复杂度都和数的值域相关,需要优化
接下来就是神级的单调性优化了
为了将$dp$和值域去相关,可以将$dp[i],f[i]$看成和$j$相关的函数!
接下来对$dp[i],f[i]$求导,同时由于$dp[1]'$单调,假设$dp[i]'$单调,且零点为$k$,则:
$dp[i+1]'(j)=2*(j-dat[i+1])+f[i+1]'(j)$,$f[i+1]'(j)=dp[i]'(j-a)(j<k+a),0(k+a\le j\ge k+b),dp[i]'(j-b)(j>k+b)$
可以发现每次$f[i+1]'$是将$dp[i]'$的函数中$[1,k)$向右移$a$,$(k,q]$向右移$b$,然后中间再加上一段零
而每次$dp[i+1]'$是在$f[i+1]'$的基础上加上一个单调递增的函数,因此$dp[i]'$单调增则$dp[i+1]'$单调增
证明了$dp[i]'$的单调性,且发现每次在原函数的基础上仅增加了一段一次函数
因此只要模拟上述过程,记录至多$n$段函数,就能最终在$O(n)$的时间内找到零点,即当前最优解
接下来考虑如何输出方案:
对于每个$i$记录下当前的零点位置
在求出$n$的零点后向回逆推每次的选择即可
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef double db;
typedef pair<db,db> P;
const int MAXN=1e4+;
P dp[MAXN*];int n,cp,tot;
db q,a,b,zp,dat[MAXN],res[MAXN],sum; int main()
{
scanf("%d%lf%lf%lf",&n,&q,&a,&b);
for(int i=;i<=n;i++) scanf("%lf",&dat[i]);
dp[++tot]=P(,*(-dat[]));
dp[++tot]=P(q+,*(q+-dat[]));
res[]=zp=dat[];cp=;
for(int i=;i<=n;i++)
{
for(int j=tot;j>cp;j--) dp[j+]=dp[j];
tot+=;dp[cp+]=P(zp+a,);dp[cp+]=P(zp+b,); for(int j=;j<=cp;j++) dp[j].X+=a;
for(int j=cp+;j<=tot;j++) dp[j].X+=b;
for(int j=;j<=tot;j++) dp[j].Y+=*(dp[j].X-dat[i]); if(dp[].Y>=) cp=,zp=dp[].X;
else
{//找零点
for(cp=;cp<tot;cp++)
if(dp[cp].Y<=&&dp[cp+].Y>) break;
zp=dp[cp].X-dp[cp].Y*(dp[cp+].X-dp[cp].X)/(dp[cp+].Y-dp[cp].Y);
}
res[i]=min(zp,q);
} for(int i=n-;i;i--)
if(res[i]+a>res[i+]) res[i]=res[i+]-a;
else if(res[i]+b<res[i+]) res[i]=res[i+]-b;
for(int i=;i<=n;i++)
printf("%.6lf ",res[i]),sum+=(res[i]-dat[i])*(res[i]-dat[i]);
printf("\n%.6lf",sum);
return ;
}
Problem E
对于$dp$模型和值域相关的题目可以考虑这样的函数式优化
如果能证明函数为单峰函数就非常方便了
同时有时候在值域很大时求单峰函数最值不能直接用三分法,会TLE(如此题)
这时就要从导数的角度考虑,看看导数有没有什么性质能不需要二分(此题导数为分段形式,记录端点来优化)
可以考虑将每个$dp[i]$和$dp[i]'$的函数图像画出来方便猜结论
[Codeforces #172] Tutorial的更多相关文章
- [Codeforces #514] Tutorial
Link: Codeforces #514 传送门 很简单的一场比赛打崩了也是菜得令人无话可说…… D: 一眼二分,发现对于固定的半径和点,能包含该点的圆的圆心一定在一个区间内,求出区间判断即可 此题 ...
- [Codeforces #210] Tutorial
Link: Codeforces #210 传送门 A: 贪心,对每个值都取最大值,不会有其他解使答案变优 #include <bits/stdc++.h> using namespace ...
- [Codeforces #196] Tutorial
Link: Codeforces #196 传送门 A: 枚举 #include <bits/stdc++.h> using namespace std; #define X first ...
- [Codeforces #174] Tutorial
Link: Codeforces #174 传送门 A: 求原根的个数,有一条性质是原根个数为$\phi(\phi(n))$,多了一个不会证的性质 如果要确定哪些是原根的话还是要枚举,不过对于每个数不 ...
- [Codeforces #190] Tutorial
Link: Codeforces #190 传送门 A: 明显答案为$n+m-1$且能构造出来 #include <bits/stdc++.h> using namespace std; ...
- [Codeforces #211] Tutorial
Link: Codeforces #211 传送门 一套非常简单的题目,但很多细节都是错了一次才能发现啊…… 还是不能养成OJ依赖症,交之前先多想想corner case!!! A: 模拟,要特判0啊 ...
- [Codeforces #192] Tutorial
Link: Codeforces #192 传送门 前两天由于食物中毒现在还要每天挂一天的水 只好晚上回来随便找套题做做找找感觉了o(╯□╰)o A: 看到直接大力模拟了 但有一个更简便的方法,复杂度 ...
- [Codeforces #201] Tutorial
Link: 传送门 代码量很少的一套思维题 A: 试一试发现最后状态一定是所有$min,max$间$gcd$的倍数 直接判断数量的奇偶性即可 #include <bits/stdc++.h> ...
- [Codeforces #188] Tutorial
Link: Codeoforces #188 传送门 A: 先全转为正数,后面就全是指数级增长了 #include <bits/stdc++.h> using namespace std; ...
随机推荐
- layer弹窗的跳转功能
1,本弹窗直接跳转父页面: @if(session('message')) <script> window.parent.location.reload(); //刷新父页面 var in ...
- Spring整合Quartz分布式调度
前言 为了保证应用的高可用和高并发性,一般都会部署多个节点:对于定时任务,如果每个节点都执行自己的定时任务,一方面耗费了系统资源,另一方面有些任务多次执行,可能引发应用逻辑问题,所以需要一个分布式的调 ...
- Python作业工资管理系统(第三周)
作业内容: 实现效果: 从info.txt文件中读取员工及其工资信息,最后将修改或增加的员工工资信息也写入原info.txt文件. 效果演示: 1. 查询员工工资 2. 修改员工工资 3. 增加新员工 ...
- C - K-inversions URAL - 1523 (dp + 线段树)
题目链接:https://cn.vjudge.net/contest/275079#problem/C 具体思路:我们可以分层的去建立,假设我们要找k层,我们可以先把满足1.2....k-1层的满足情 ...
- jq时间日期插件的使用-datetimepicker
分三步 首先引入各种包 然后搞哥容器用id 然后加入一段js 实例: 下载:http://files.cnblogs.com/files/wordblog/datetimepicker-maste ...
- Metlnfo CMS全版本漏洞收集
根据https://www.seebug.org/appdir/MetInfo 进行书写. [版本:Metlnfo 4.0] 漏洞标题:Metlnfo cms任意用户密码修改 漏洞文件:member/ ...
- module 'pytest' has no attribute 'allure'问题解决
安装allure后执行命令后报错module 'pytest' has no attribute 'allure' C:\Users\Desktop\xin>pytest -s -q --all ...
- Nginx实现404页面的几种方法【转】
一个网站项目,肯定是避免不了404页面的,通常使用Nginx作为Web服务器时,有以下集中配置方式,一起来看看. 第一种:Nginx自己的错误页面 Nginx访问一个静态的html 页面,当这个页面没 ...
- Elasticsearch5.0 安装问题集锦【转】
转自 Elasticsearch5.0 安装问题集锦 - 代码&优雅着&生活 - 博客园http://www.cnblogs.com/sloveling/p/elasticsearch ...
- URAL题解三
URAL题解三 URAL 1045 题目描述:有\(n\)个机场,\(n-1\)条航线,任意两个机场有且只有一种方案联通.现有两个恐怖分子从\(m\)号机场出发,第一个人在机场安装炸弹,乘坐飞机,引爆 ...