NOIp模拟61
水博客太快乐了
烤场
昨天没睡醒,开题很困。。。
看了 \(t1,t2,t3\) ,没什么思路,感觉 \(t2\) 应该会有结论,但是没深想,决定先干 \(t1\) ,想了半天;欧拉回路之类的东西。。。。
后来才知道是特别巧妙的思维题。。。
ycx :这不是套路题吗。
然后水了前三题的暴力,看 \(t4\) , \(dp\) 式显然,维护了一个玄学下凸包,然后二分斜率,结果最后不知道为啥和暴力一个分。。。
分数
\(t1\ 30pts\ +\ t2\ 20pts\ +\ t3\ 0pts\ +\ t4\ 50pts\ =\ 100pts\)
比预估低好多,想都没想就认为 \(O(n^2 \log n)\) 能过,实际上稍加优化就可以。
\(t2\) 其实很水,但是因为长时间看 \(t1,t4\) 最终没有太长时间写。
题解
A. 交通
以为这题考欧拉回路,结果考的是建图。。。
将每个点看成一条边,然后将出入同一个点的两条边之间连一条边,表示这两条边只能选一条,得到的新图最终势必会形成许多联通块,且每个联通块都是一个含有偶数个点的环(因为每个点都有两条边相连),不难发现新图中每个联通块有有且仅有两种可能的方式(因为选了一个点,则与这个点相邻的两个点均不能选,不选一个点,则与这个点相邻的两个点必须选),设总共有 \(x\) 个联通块,则最终答案为 \(2^x\) 。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10, mod=998244353;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n;
vector<int> in[N], out[N], l[N];
int num;
bool vis[N];
inline int qp(int n, int m){
int ans=1;
while(m){
if(m&1) ans=1ll*ans*n%mod;
m>>=1; n=1ll*n*n%mod;
}
return ans;
}
void dfs(int u){
vis[u]=1;
for(int v : l[u])
if(!vis[v]) dfs(v);
}
signed main(void){
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
n=read(); int x, y;
for(int i=1; i<=n<<1; ++i){
x=read(), y=read();
out[x].push_back(i);
in[y].push_back(i);
}
for(int i=1; i<=n; ++i){
l[in[i][0]].push_back(in[i][1]);
l[in[i][1]].push_back(in[i][0]);
l[out[i][0]].push_back(out[i][1]);
l[out[i][1]].push_back(out[i][0]);
}
for(int i=1; i<=n<<1; ++i){
if(vis[i]) continue;
dfs(i); ++num;
}
printf("%d\n", qp(2, num));
}
B. 小P的单调数列
本场比赛最简单的题,然而因为没有怎么看,最终只的了 \(20pts\) 。
不难发现,其实在选择时,最多会选择两个区间。因为若要选择多个序列求平均值,最终一定小于这些序列中的最大值,所以一定会选择一个单调递增序列或先选择一个单调递增再选择一个单调递减(因为第一个序列必须选择单调递增)。
显然可以直接用树状数组实现。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n;
int a[N], b[N];
ll f[N], rf[N];
double ans;
class TRE{
private :
ll a[N]; int n;
inline int lb(int x) { return x&-x; }
public :
inline void init(int x) { n=x; }
inline void add(int p, ll x) { for(; p<n; p+=lb(p)) a[p]=max(a[p], x); }
inline ll que(int p) { ll ans=0; for(; p; p-=lb(p)) ans=max(ans, a[p]); return ans; }
inline void clear() { memset(a, 0, sizeof a); }
}t;
signed main(void){
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
n=read(); t.init(n+2);
for(int i=1; i<=n; ++i) b[i]=a[i]=read();
sort(b+1, b+1+n); int len=unique(b+1, b+1+n)-b-1;
for(int i=1; i<=n; ++i) a[i]=lower_bound(b+1, b+1+len, a[i])-b;
for(int i=1; i<=n; ++i){
f[i]=t.que(a[i])+b[a[i]];
t.add(a[i]+1, f[i]);
ans=max(ans, (double)f[i]);
}
t.clear();
for(int i=n; i; --i){
rf[i]=t.que(a[i])+b[a[i]];
t.add(a[i]+1, rf[i]);
}
t.clear();
for(int i=n; i; --i){
ans=max(ans, (double)(f[i]+t.que(a[i]))/2);
t.add(a[i]+1, rf[i]);
}
printf("%.3lf\n", ans);
return 0;
}
C. 矩阵
比较玄学的构造题,因为懒,所以直接挂上巨佬的博客。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1010;
inline ll read(){
ll f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, m;
ll a[N][N], l[N], h[N], lh[N<<1];
int main(void){
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
n=read(), m=read();
for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j)
a[i][j]=read();
l[1]=a[2][2]-a[1][1]; lh[n]-=a[2][2];
for(int i=2; i<=n; ++i) lh[n-i+1]-=(a[i][1]+l[i]), l[i+1]-=(a[i+1][2]+lh[n-i+1]);
for(int i=2; i<=m; ++i) lh[n+i-1]-=(a[1][i]+l[1]+h[i]), h[i+1]-=(a[2][i+1]+lh[n+i-1]);
for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j)
if(a[i][j]+l[i]+h[j]+lh[j-i+n]) { puts("-1"); return 0; }
printf("%d\n", 2*(n+m)-1);
for(int i=1; i<=n; ++i) printf("1 %d %lld\n", i, l[i]);
for(int i=1; i<=m; ++i) printf("2 %d %lld\n", i, h[i]);
for(int i=1; i<n+m; ++i) printf("3 %d %lld\n", i-n, lh[i]);
return 0;
}
D. 花瓶
烤场上写的暴力维护凸包加二分斜率,然而因为带 \(\log\) ,大力 \(Tle\) 了。。。
烤后想了很长时间如何去掉 \(\log\) ,在题解的启发下终于过了。。。然而还是没有看懂题解
大概说一下我的做法。。。
首先 \(dp\) 的状态是显然的,设 \(f_{i,j}\) 表示 \(dp\) 到 \(i\) ,上一个区间的右节点为 \(j\) 时的最大答案。
转移也很显然(式中的 \(a\) 为原题中 \(a\) 数组的前缀和) :
\(\displaystyle f_{i,j}=max \ f_{j,k}+(a_i-a_j)(a_j-a_k)\)
拆式子 :
\(\displaystyle f_{i,j}=max \ a_i(a_j-a_k)+f_{j,k}-a_j^2+a_j a_k\)
将 \(a_j-a_k\) 看作横坐标,\(f_{j,k}-a_j^2+a_j a_k\) 看作纵坐标,对于每个 \(j\) ,维护由点 \((a_j-a_k, f_{j,k}-a_j^2+a_j a_k)\) 构成的斜率递减的下凸包,此时最裸的思路是更新每个 \(i\) 的时候直接二分斜率即可,可是二分斜率带一个 \(\log\) 考虑如何消去其影响。
为什么不能直接贪心的删除前面的点,而要二分求斜率呢?
因为有些点的值是负数,导致 \(a_i\) 不一定是递增的,那直接将 \(a_{i}\) 排序即可。。。
感觉有点说不清楚,具体看代码吧。。。
code
#include<bits/stdc++.h>
using namespace std;
#define ll double
const int N=5010;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, top;
struct PI{
ll fi; int se;
friend bool operator < (const PI &x, const PI &y) { return x.fi==y.fi ? x.se > y.se : x.fi < y.fi; }
}a[N];
struct Pi{
ll fi, se;
friend bool operator < (const Pi &x, const Pi &y) { return x.fi==y.fi ? x.se > y.se : x.fi < y.fi; }
};
vector<Pi> vec;
vector<Pi> stk;
inline double slope(int y) { return -(vec[y].se-vec[y+1].se)/(vec[y].fi-vec[y+1].fi); }
inline double Slope(double x0, double y0, double x1, double y1) { return -(y0-y1)/(x0-x1); }
ll f[N][N], ans, s[N];
signed main(void){
freopen("d.in", "r", stdin);
freopen("d.out", "w", stdout);
n=read(); int x;
for(int i=1; i<=n; ++i){
x=read(); s[i]=s[i-1]+x;
a[i]=(PI){a[i-1].fi+x, i};
}
sort(a, a+1+n);
for(int i=0; i<=n; ++i){
stk.clear(); vec.clear();
if(!i) stk.push_back((Pi){ 0, 0});
for(int j=n; ~j; --j){//保证横坐标递增
if(a[j].se>=i) continue;//找到二维平面上的每一个点
stk.push_back((Pi){s[i]-a[j].fi, f[i][a[j].se]-s[i]*s[i]+s[i]*a[j].fi});
}
for(Pi v : stk){
if(!vec.empty()&&vec.back().fi==v.fi&&v.se>=vec.back().se) vec.pop_back();
while(vec.size()>=2&&slope(vec.size()-2)>=Slope(v.fi, v.se, vec.back().fi, vec.back().se)) vec.pop_back();
vec.push_back(v);
}//暴力求得凸包
int len=0;
for(int j=0; j<=n; ++j){
if(a[j].se<=i) continue;
while(len<(signed)vec.size()-1&&slope(len)<a[j].fi) ++len;
f[a[j].se][i]=vec[len].fi*a[j].fi+vec[len].se;
}//用当前凸包更新每个它之后点的答案
}
for(int i=0; i<n; ++i) ans=max(ans, f[n][i]);
printf("%.0lf\n", ans);
return 0;
}