考炸了!!!!!!
好xie啊,考成这个鬼样子,怕今年noip也是要爆萎了。
还没考完,来发总结了,今天考试真的不想说什么了……
还是认真写题解吧……
先按题目难度来排排,考试时只写出了problem2,有点那啥,那就先写这题的题解
prob2:集合(set)
题目大意:有一棵树,树上每个节点对应着一个集合,开始时都是空集,对每个节点从根到叶子进行一些操作(加入元素或删除元素),最后输出每个节点对应集合的大小
我的做法:树上操作,顺序又是从根到叶子,首先想到的就是\(dfs\)一波。然后对于集合的去重与删元素操作,蒟蒻想到的做法是建立一棵值域线段树,然后按\(dfs\)顺序进行该节点的操作,当遍历完这一个节点为根的子树后,将该节点的操作还原,这样将不会影响其他子树的答案输出。
貌似\(zsb\)大佬有只用\(dfs\)实现的方法,但思路貌似差不多,其他几道题炸了还要整理,就没看他的方法了
好吧,我也没想到这方法\(RE\)飞了。仔细想想,其实早应该想到的,即使是权值线段树,节点数也应该开\(4\)倍,第几次了!!!!!
ririririri……
汗,这下真的考飞了、
代码如下(改完的):
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 110000
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x*f;
}
struct line_tree{int l,r,sz,ans;}lt[xx<<2];
int num=0,res[xx],now;
vector<int>e[xx],ee[xx],eee[xx];
bool vis[xx];
inline void build(int k,int i,int j)
{
lt[k].l=i;lt[k].r=j;
if(i==j) return;
int q=k<<1,mid=(i+j)>>1;
build(q,i,mid);
build(q+1,mid+1,j);
}
inline void up(int k)
{
int q=k<<1;
lt[k].ans=lt[q].ans+lt[q+1].ans;
}
inline void add(int k,int x,int c)
{
if(lt[k].l==lt[k].r)
{
now=lt[k].sz;
lt[k].sz+=c;
lt[k].sz=max(lt[k].sz,0);
lt[k].sz=min(lt[k].sz,1);
lt[k].ans=lt[k].sz?1:0;
return;
}
int q=k<<1,mid=lt[q].r;
if(x<=mid) add(q,x,c);
else add(q+1,x,c);
up(k);
}
inline void change(int k,int x,int c)
{
if(lt[k].l==lt[k].r)
{
lt[k].sz=c;
lt[k].ans=lt[k].sz?1:0;
return;
}
int q=k<<1,mid=lt[q].r;
if(x<=mid) change(q,x,c);
else change(q+1,x,c);
up(k);
}
inline void dfs(int g)
{
fur(i,0,(int)ee[g].size()-1)
{
if(ee[g][i]<0) add(1,-ee[g][i],-1);
else add(1,ee[g][i],1);
eee[g].push_back(now);
}
res[g]=lt[1].ans;
fur(i,0,(int)e[g].size()-1) if(!vis[e[g][i]]) vis[e[g][i]]=true,dfs(e[g][i]);
fur(i,0,(int)ee[g].size()-1)
{
if(ee[g][i]<0) change(1,-ee[g][i],eee[g][i]);
else change(1,ee[g][i],eee[g][i]);
}
}
int main()
{
int n=in;
fur(i,1,n-1)
{
int x=in,y=in;
e[x].push_back(y);
e[y].push_back(x);
}
fur(i,1,n)
{
int k=in;
fur(j,1,k)
{
int x=in;
ee[i].push_back(x);
}
}
build(1,1,n);
vis[1]=true;
dfs(1);
fur(i,1,n) printf("%d\n",res[i]);
return 0;
}
正解:思想如上,但数据结构由值域线段树变为桶,做到\(O(1)\)修改。
这下复杂度就对了,时间空间都舒服很多,而且不会有恶心的情况,代码如下:
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 110000
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x*f;
}
int num=0,res[xx],before;
vector<int>e[xx],ee[xx],eee[xx];
bool vis[xx],cnt[xx];
inline void dfs(int g)
{
fur(i,0,(int)ee[g].size()-1)
{
before=cnt[abs(ee[g][i])];
if(ee[g][i]<0) cnt[-ee[g][i]]=false;
else cnt[ee[g][i]]=true;
num+=(cnt[abs(ee[g][i])]-before);
eee[g].push_back(before);
}
res[g]=num;
fur(i,0,(int)e[g].size()-1) if(!vis[e[g][i]]) vis[e[g][i]]=true,dfs(e[g][i]);
fur(i,0,(int)ee[g].size()-1)
{
before=cnt[abs(ee[g][i])];
if(ee[g][i]<0) cnt[-ee[g][i]]=eee[g][i];
else cnt[ee[g][i]]=eee[g][i];
num+=(cnt[abs(ee[g][i])]-before);
}
}
int main()
{
int n=in;
fur(i,1,n-1)
{
int x=in,y=in;
e[x].push_back(y);
e[y].push_back(x);
}
fur(i,1,n)
{
int k=in;
fur(j,1,k)
{
int x=in;
ee[i].push_back(x);
}
}
vis[1]=true;
dfs(1);
fur(i,1,n) printf("%d\n",res[i]);
return 0;
}
prob1:中位数(median)
题目大意:给出一个1~n的排列,询问每个含有奇数个元素的区间的中位数m与区间左右两端的乘积的和
40分部分分:这次部分分还是给的蛮足的,1k不到的码量足足有40分,这没什么好说,纯暴力,每次重排,找中位数。时间复杂度\(O(n^3logn)\)
暴力搞法:在\(xgzc\)大佬的帮助下改进了蒟蒻未成形的幻想,易知:共\(\dfrac{n^2}{2}\)个区间,统计中位数的话蒟蒻想整体二分一波求区间\(mid\)小值,但大佬说这样时间复杂度为\(O(n^2logn)\)。而且常数大,不好卡,所以是失败的。而用两个堆(一个大根,一个小根)用来维护中位数可以做到相同的复杂度,并且常数可以小到忽略不计。又由于本题可以开\(O2\),\(n\)值域较小(\(<=1e4\)),故可以如此暴力骚过。
好吧,也并没有过,被卡了,数据还是大了,学长太强大了 qwq
代码如下:
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 11000
#define ll long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x*f;
}
priority_queue<ll>e;
priority_queue<ll,vector<ll>,greater<ll> >f;
ll a[xx],ans=0;
int main()
{
int n=in;
fur(i,1,n) a[i]=in;
fur(i,1,n)
{
while(!e.empty()) e.pop();
while(!f.empty()) f.pop();
e.push(a[i]);
ans+=i*i*e.top();
for(int j=i+2;j<=n;j+=2)
{
int now=0;
fur(k,j-1,j)
{
if(!now)
{
if(f.empty()||a[k]<=f.top()) e.push(a[k]);
else
{
e.push(f.top());
f.pop();
f.push(a[k]);
}
now++;
}
else
{
if(a[k]>=e.top()) f.push(a[k]);
else
{
f.push(e.top());
e.pop();
e.push(a[k]);
}
}
}
ans+=i*j*e.top();
}
}
printf("%lld\n",ans);
return 0;
}
正解:开双向链表,可以做到将中位数\(O(1)\)地转移。思想与上一样,不多说了,说多了都是泪。上代码(用的学长的题解,看不懂,算了):
#include <iostream>
#include <vector>
using namespace std;
typedef long long int64;
int main() {
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
int n; cin >> n;
vector<int> element(n);
for (int i = 0; i < n; i += 1) {
cin >> element[i];
}
vector<pair<int, int>> neighbours(n + 1);
int64 result = 0;
for (int i = 0; i < n; i += 1) {
//如果索引在[i,n]范围内,则该值有效
vector<bool> valid(n + 1, false);
for (int j = i; j < n; j += 1) {
valid[element[j]] = true;
}
valid[n + 1] = true;
int last = 0;
//值的数目>中间值,较低值的数目<=中间值
//mid->中间值
int bigger = (n - i), lower = 0, mid = 0;
// 计算每个值,左边和右边的下一个有效值
for (int j = 1; j <= n; j += 1) {
if (not valid[j]) {
continue;
}
if (bigger > lower) {
mid = j;
bigger -= 1;
lower += 1;
}
// 最后一个元素将是当前元素左边的元素
// 最后一个元素右边的元素将是这个元素
neighbours[j].first = last;
neighbours[last].second = j;
last = j;
}
for (int j = n -1 ; j >= i; j -= 1) {
if (lower == bigger + 1) {
// this can be any formula, whatsoever
result += (int64) (j + 1) * (i + 1) * mid;
}
// 值位于擦除值的左侧和右侧
int left = neighbours[element[j]].first;
int right = neighbours[element[j]].second;
// 从“likef列表”中删除此列表
neighbours[left].second = right;
neighbours[right].first = left;
// 更新中间值、较大或较小的值。
if (element[j] == mid) {
bigger -= 1;
mid = right;
} else if (element[j] < mid) {
lower -= 1;
} else {
bigger -= 1;
}
while (lower > bigger) {
mid = neighbours[mid].first;
bigger += 1;
lower -= 1;
}
// move the median to the right
while (bigger > lower) {
mid = neighbours[mid].second;
bigger -= 1;
lower += 1;
}
}
}
cout << result << '\n';
return 0;
}
其他解法:请容许我向大家隆重介绍\(yyz\)大佬的解法,简直与美丽的莫队有着异曲同工之妙,同样是通过\(O(1)\)地删、增值来转移\(mid\)指向的位置,实现了与正解同样优秀的\(O(n^2)\)算法,爆踩\(std\)。\(orz\)……
详情见代码:
#include<iostream>
#include<cstdio>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;++i)
#define int long long
#define xx 10010
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x*f;
}
int cnt[xx],a[xx];
signed main()
{
int n=in;
fur(i,1,n) a[i]=in;
int l=1,r=0,mid=0,cnnt=0,ans=0;
fur(tl,1,n)
{
for(int tr=tl;tr<=n;tr+=2)
{
while(l<tl)
{
if(a[l]<=mid) --cnnt;
--cnt[a[l++]];
}
while(r<tr)
{
++r;
if(a[r]<=mid) ++cnnt;
++cnt[a[r]];
}
while(r>tr)
{
if(a[r]<=mid) --cnnt;
--cnt[a[r--]];
}
int need=((tr-tl)>>1)+1;
if(need==1)
{
mid=a[tl];
cnnt=1;
ans+=tr*tl*mid;
continue;
}
while(cnnt<need) cnnt+=cnt[++mid];
while(cnnt>need)
{
if(cnnt-cnt[mid]<need) break;
cnnt-=cnt[mid--];
}
while(cnt[mid]==0) --mid;
ans+=tl*tr*mid;
}
}
printf("%lld\n",ans);
return 0;
}
prob4:星空(stars)
题目大意:给定平面上一些点对,每个点都有其对应权值,求给定大小的矩阵最大点权和。
扫描线裸题(可惜我不会)
还是先给40分暴力做法吧:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define xx 55000
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x*f;
}
struct point{int x,y,z;}dot[xx];
int ans=0;
inline bool cmp(point a,point b){return (a.x^b.x)?(a.x<b.x):(a.y<b.y);}
int main()
{
int n=in,w=in,h=in;
fur(i,1,n)
{
dot[i].x=in;
dot[i].y=in;
dot[i].z=in;
}
sort(dot+1,dot+n+1,cmp);
fur(i,1,n)
{
int j=i+1,tmp=dot[i].z;
while(j<=n&&dot[j].x-dot[i].x<=w)
{
if(dot[j].y-dot[i].y<=h&&dot[j].y>=dot[i].y) tmp+=dot[j].z;
j++;
}
int j1=i+1,tmp1=dot[i].z;
while(j1<=n&&dot[j1].x-dot[i].x<=w)
{
if(dot[i].y-dot[j1].y<=h&&dot[j1].y<=dot[i].y) tmp1+=dot[j1].z;
j1++;
}
ans=max(ans,max(tmp,tmp1));
}
printf("%d\n",ans);
return 0;
}
好了,水程序就用来水字数了,接下来还是要进入正题
正解:扫描线加线段树优化,详情见代码:
#include <cstdio>
#include<cstring>
#include <iostream>
#include <algorithm>
#define ls (x<<1)
#define rs (x<<1|1)
using namespace std;
typedef long long ll;
ll geti(){
char ch=getchar(),k=1;ll ret=0;
while((ch<'0' || ch>'9') && ch!='0')ch=getchar();
if(ch=='-')k=0,ch=getchar();
while(ch>='0' && ch<='9')ret=ret*10+ch-'0',ch=getchar();
return k?ret:-ret;
}
const int maxn = 100000 + 1000;
struct XDS{
ll mx[maxn*8],lazy[maxn*8];
void pushdown(int x){
mx[ls]+=lazy[x];mx[rs]+=lazy[x];
lazy[ls]+=lazy[x];lazy[rs]+=lazy[x];
lazy[x]=0;
}
void update(int x,int l,int r,int xl,int xr,ll v){
if(xl==l && r==xr){
mx[x]+=v;lazy[x]+=v;return;
}
int mid=(l+r)>>1;
if(xr<=mid)update(ls,l,mid,xl,xr,v);
else if(xl>mid)update(rs,mid+1,r,xl,xr,v);
else update(ls,l,mid,xl,mid,v),update(rs,mid+1,r,mid+1,xr,v);
mx[x]=max(mx[ls],mx[rs])+lazy[x];
}
ll query(int x,int l,int r,int xl,int xr){
if(l==xl && r==xr){
return mx[x];
}
int mid=(l+r)>>1;
if(xr<=mid)return query(ls,l,mid,xl,xr)+lazy[x];
else if(xl>mid)return query(rs,mid+1,r,xl,xr)+lazy[x];
else return max(query(ls,l,mid,xl,mid),query(rs,mid+1,r,mid+1,xr))+lazy[x];
}
}t;
int n,w,h;
struct star{
ll x,y,ty,l;
}a[maxn];
ll hs[maxn];
bool cmpx(const star &x,const star &y){return x.x<y.x;}
bool cmpy(const star &x,const star &y){return x.ty<y.ty;}
int main()
{
scanf("%d%d%d",&n,&w,&h)
for(int i=1;i<=n;i++)
{
a[i*2-1].x=geti();a[i*2-1].ty=geti();a[i*2-1].l=geti();
a[i*2].x=a[i*2-1].x+w+1;a[i*2].ty=a[i*2-1].ty;a[i*2].l=-a[i*2-1].l;
}
n=n*2;
sort(a+1,a+1+n,cmpy);
int s=0;
for(int i=1;i<=n;i++)
{
if(i!=1 && a[i].ty==a[i-1].ty) a[i].y=s;
else
{
if(a[i].ty-hs[s]>1) ++s,hs[s]=hs[s-1]+1;
++s;hs[s]=a[i].ty;
a[i].y=s;
}
}
hs[++s]=a[n].ty+1;
sort(a+1,a+1+n,cmpx);
ll ans=0;
for(int i=1;i<=n;i++)
{
int l=a[i].y,r=s,mid,rt=-1;
ll cur=a[i].ty+h;
while(l<=r)
{
mid=(l+r)>>1;
if(hs[mid]<=cur) rt=mid,l=mid+1;
else r=mid-1;
}
t.update(1,1,s,a[i].y,rt,a[i].l);
if(a[i].x!=a[i+1].x || i==n) ans=max(ans,t.query(1,1,s,1,s));
}
printf("%lld\n",ans);
return 0;
}
prob3:星际争霸(craft)
题目大意:给定一个\(c*r\)的矩阵,其中的格子有可通过与不可通过两种,给定主人公与两个敌人的位置与二者血量,求是否可以赢得游戏与赢得游戏的最小回合数
\(MDzhizhang\),学长说这题是道\(sb\)爆搜,可以打爆搜切题,不想说什么了。好像没什么时间了,最后一个直接\(kuai\)题解的算了(标程用的\(dp\)):
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
LL gi () {
LL ret=0; char ch=getchar();
while((ch<'0' || ch>'9') && ch!='-') ch=getchar();
char c=ch=='-'?getchar():ch;
while(c>='0' && c<='9') ret=ret*10+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
int n,m,tl,pos[37][37],w[37][37],near[37][37],h_m,h_z,dx[4]={0,-1,0,1},dy[4]={-1,0,1,0};
int dp[55][37][37][37][18];
char mp[7][7];
int go (int p,int d) {
int x = (p-1)/m, y = (p-1)%m;
int nx = x + dx[d], ny = y + dy[d];
if(nx < 0 || nx > n || ny < 0 || ny > m || mp[nx][ny]=='1') return -1;
return pos[nx][ny];
}
int zgo (int p,int g) {
if(near[p][g] || !p) return p;
int res = -1, dis = 1e9;
for(int i=0;i<=3;i+=1) {
int c = go(p,i);
if(c==-1) continue;
if(w[c][g] < dis) dis = w[c][g], res = c;
}
if(dis > 100) return p;
return res;
}
void upd (int &x, int y) {
x = x < y ? x : y;
}
int main (int argc, char* argv[]) {
freopen("craft.in","r",stdin);
freopen("craft.out","w",stdout);
n = gi(), m = gi(), tl = gi();
for(int i=0;i<n;i+=1) scanf("%s",mp[i]);
h_m = gi(); h_z = gi();
int p1 = -1, p2 = -1, p3 = -1;
for(int i=0;i<n;i+=1) {
for(int j=0;j<m;j+=1) {
pos[i][j] = i*m+j+1;
if(mp[i][j]=='z' || mp[i][j]=='Z') {
if(p2 == -1) p2 = pos[i][j]; else p3 = pos[i][j];
}
else if(mp[i][j]=='M') p1 = pos[i][j];
}
}
memset(w,60,sizeof w);
for(int i=0;i<n;i+=1) {
for(int j=0;j<m;j+=1) {
if(mp[i][j] == '1') continue;
if(i && mp[i-1][j] != '1') w[pos[i][j]][pos[i-1][j]] = 1;
if(i!=n-1 && mp[i+1][j] != '1') w[pos[i][j]][pos[i+1][j]] = 1;
if(j && mp[i][j-1] != '1') w[pos[i][j]][pos[i][j-1]] = 1;
if(j!=m-1 && mp[i][j+1] != '1') w[pos[i][j]][pos[i][j+1]] = 1;
w[pos[i][j]][pos[i][j]] = 0;
}
}
int sz = n*m;
for(int i=1;i<=sz;i+=1) {
for(int d=0;d<=3;d+=1)
if(go(i,d) != -1) near[i][go(i,d)] = 1;
}
for(int k=1;k<=sz;k+=1)
for(int i=1;i<=sz;i+=1)
for(int j=1;j<=sz;j+=1) w[i][j] = min(w[i][j], w[i][k] + w[k][j]);
memset(dp,60,sizeof dp);
dp[0][p1][p2][p3][h_m] = h_z * 2;
dp[0][p1][p3][p2][h_m] = h_z * 2;
for(int i=0;i<tl;i+=1)
for(int t1=1;t1<=sz;t1+=1)
for(int t2=0;t2<=sz;t2+=1)
for(int t3=0;t3<=sz;t3+=1)
for(int h=1;h<=h_m;h+=1) {
int cur = dp[i][t1][t2][t3][h];
if(cur > 100) continue;
// 1. move
for(int d=0;d<=3;d+=1) {
int n1 = go(t1,d);
if(n1 == -1 || n1 == t2 || n1 == t3) continue;
int v = 0;
int n2 = zgo(t2,n1), n3 = zgo(t3,n1);
if(near[t2][n1]) ++v;
if(near[t3][n1] && t2 != t3 && cur > h_z) ++v;
if(h-v > 0) {
upd(dp[i+1][n1][n2][n3][h-v], cur);
}
}
// 2. attack
int n2 = zgo(t2,t1), n3 = zgo(t3,t1);
if(cur == 1) {
printf("WIN\n%d",i+1); exit(0);
}
int v = 0;
if(near[t2][t1]) ++v;
if(near[t3][t1] && t2 != t3 && cur-1 > h_z) ++v;
if(h-v > 0) {
if(cur-1 > h_z) {
upd(dp[i+1][t1][n2][n3][h-v], cur-1);
}
else {
upd(dp[i+1][t1][n2][0][h-v], cur-1);
}
}
}
puts("LOSE");
return 0;
}
希望明天图论别水吧