Link:

Codeforces #172 传送门

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的更多相关文章

  1. [Codeforces #514] Tutorial

    Link: Codeforces #514 传送门 很简单的一场比赛打崩了也是菜得令人无话可说…… D: 一眼二分,发现对于固定的半径和点,能包含该点的圆的圆心一定在一个区间内,求出区间判断即可 此题 ...

  2. [Codeforces #210] Tutorial

    Link: Codeforces #210 传送门 A: 贪心,对每个值都取最大值,不会有其他解使答案变优 #include <bits/stdc++.h> using namespace ...

  3. [Codeforces #196] Tutorial

    Link: Codeforces #196 传送门 A: 枚举 #include <bits/stdc++.h> using namespace std; #define X first ...

  4. [Codeforces #174] Tutorial

    Link: Codeforces #174 传送门 A: 求原根的个数,有一条性质是原根个数为$\phi(\phi(n))$,多了一个不会证的性质 如果要确定哪些是原根的话还是要枚举,不过对于每个数不 ...

  5. [Codeforces #190] Tutorial

    Link: Codeforces #190 传送门 A: 明显答案为$n+m-1$且能构造出来 #include <bits/stdc++.h> using namespace std; ...

  6. [Codeforces #211] Tutorial

    Link: Codeforces #211 传送门 一套非常简单的题目,但很多细节都是错了一次才能发现啊…… 还是不能养成OJ依赖症,交之前先多想想corner case!!! A: 模拟,要特判0啊 ...

  7. [Codeforces #192] Tutorial

    Link: Codeforces #192 传送门 前两天由于食物中毒现在还要每天挂一天的水 只好晚上回来随便找套题做做找找感觉了o(╯□╰)o A: 看到直接大力模拟了 但有一个更简便的方法,复杂度 ...

  8. [Codeforces #201] Tutorial

    Link: 传送门 代码量很少的一套思维题 A: 试一试发现最后状态一定是所有$min,max$间$gcd$的倍数 直接判断数量的奇偶性即可 #include <bits/stdc++.h> ...

  9. [Codeforces #188] Tutorial

    Link: Codeoforces #188 传送门 A: 先全转为正数,后面就全是指数级增长了 #include <bits/stdc++.h> using namespace std; ...

随机推荐

  1. 使用Docker 快速搭建nuget本地服务器,Hosting private nuget server using docker in seconds!

    Server #below line automatically creates the folder, mount the volumes and maps the ports. docker ru ...

  2. 网易android开发面试题及心得

    前几天面试网易android开发,总体感觉问题难度一般.怪我自己没有好好梳理知识,尤其是基础,后面就没消息了... 笔试: 1.描述Activity 生命周期 2.什么是ANR,如何规避? 3.描述a ...

  3. React Native 与 夜神模拟器的绑定

    之前一直用真机去调试, 每回更新一次都需要手动摇晃手机后才能reload JS, OMG,太麻烦了. 后来寻思模拟器网上推荐用Geny...什么的模拟器,但是那个模拟器还需要VBox一起用. 有点麻烦 ...

  4. 判断Selenium加载完成

    How do you make Selenium 2.0 wait for the page to load? You can also check pageloaded using followin ...

  5. 工作当中遇到的ssh错误

    [root@1bcc1d3f9666 externalscripts]# /usr/sbin/sshd Could not load host key: /etc/ssh/ssh_host_rsa_k ...

  6. 9.Python3标准库--数据压缩与归档

    ''' 尽管现代计算机系统的存储能力日益增长,但生成数据的增长是永无休止的. 无损(lossless)压缩算法以压缩或解压缩数据花费的时间来换取存储数据所需要的空间,以弥补存储能力的不足. Pytho ...

  7. str.format() 格式化字符串函数

    语法 它通过{}和:来代替%. “映射”示例 通过位置 In [1]: '{0},{1}'.format('kzc',18) Out[1]: 'kzc,18' In [2]: '{},{}'.form ...

  8. insta php-fpm 的配置

    [global]pid = /www/wdlinux/phps/70/var/run/php-fpm.piderror_log = /www/wdlinux/phps/70/var/log/php-f ...

  9. 保存进程的pid 文件目录/var/run/

    http://blog.ddup.us/?p=110 http://blog.csdn.net/fyinsonw/article/details/4113124 首先声明这不是愚人节消息,事实上这个消 ...

  10. javascript当中的this详解

    总结this的3个规则: this是调用上下文,上下文被创建或者初始化时才确定 非严格模式:this是全局对象:严格模式:this是undefined 函数调用 a. 以函数形式调用的函数通常不使用t ...