欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

NOIp系列题目及CF小结

程序员文章站 2024-03-20 13:25:52
...

长期更新中2333

2018/7/2

先看一下昨晚的cf

Codeforces Round #493 (Div. 2)

A. Balloons

这个题。。。直接模拟233

B. Cutting

来一下奇加偶减。统计合适位置然后排个序,贪心减

C. Convert to Ones

连续的0或者1显然可以表示为一个

那么可能的情况就只有0101010……或者101010……

交换一次可以少一个需要覆盖的区间。

所以统计0区间的个数然后贪心的翻转或者覆盖

if(Rev<Cov) {
    ans=Rev*(cnt-1);
    ans+=Cov;
}
else ans=Cov*cnt;

就这样

D. Roman Digits

打表。。。发现11个之后差值均为49。。。。

人类智慧啊

#include<bits/stdc++.h>
using namespace std;
long long n,a[20]={0,4,10,20,35,56,83,116,155,198,244,292};
int main() {
    cin>>n;
    if(n<=11) cout<<a[n];
    else cout<<a[11]+(n-11ll)*49ll;
    return 0;
}

然后是今天的NOIp模拟。

T1是一个杨辉三角,预处理一下阶乘和逆元就好

void prepare(int lim) {
    fac[0]=1; inv[0]=1;
    for(int i=1;i<=lim;++i) fac[i]=fac[i-1]*i%mod;
    inv[lim]=Ksm(fac[lim],mod-2);
    for(int i=lim-1;i;--i) inv[i]=(i+1)*inv[i+1]%mod;
    return ;
}//阶乘求逆元的独特技巧2333

T2。。。

字典中有 m 个单词,每个单词长度不超过50。

读题的重要性。。。这个题目是一个很菜的Trie树。

由于单词长度不超过50,所以对于匹配串每一个位置都暴力匹配一次进行dp

T3

一道构造题。。。所以说贪心可以得80分,还是不要去挑战人类智慧算了

/****************************************我是日期的分割线****************************************************/

2018/7/3

T1

一道不是很难的数学题,搞一下阶乘的质因数就好

 

for(long long i=1;i<=psize;++i) {

long long cnt=0,temp=prime[i];

while(temp<=n) {

cnt+=n/temp;

temp*=prime[i];

}

if(cnt&1) --cnt;

ans=ans*Ksm(prime[i],cnt)%mod;

}

T2

 

情报传输

        从前有一个间谍组织,一共派遣了 n 个人潜伏在 E 国刺探情报。为了防止情报泄露,一个间谍可能只认识其他几个人。
  认识的人之间有上下级的关系,并且只有一个头目为 1 号,也就是说他们的关系形成了一棵有根树的结构,每个人既可以向直接上级传递情报,也可以向直接下级传递情报。一个人拿到情报后需要一天的处理时间才能传递这个情报,传递情报的时间忽略不计。
  因为各种各样的突发情况,头目可能会变更,或者两个人之间暂时联系不上了。
  现在给出 m 个突发事件:可能是头目发生了变更;或者是一个人拿到了情报,但他到头目的传递过程中有两个人之间失去了联系。求他把情报传给所有能接收到的人手里的时间。

  注意,第一个人拿到情报的人不需要处理情报,其他人拿到情报后处理完才算接收完情报。更换头目是永久的,会影响后面的查询;失去联系是暂时的,仅影响当前查询,不影响后面的查询。

1<=n,m<=200000

这个题啊,本来是NOIp模拟来着。听说正解是什么树上的倍增再加上根与节点位置的讨论做出来的。

不过我最近沉迷数据结构啊,所以就无脑暴力了一波

首先树链剖分方便计算两点距离然后将所用的询问离线下来,用LCT预处理出每一次询问时断掉了哪条边。

找树上的k级祖先可以Access之后在Splay上二分。

然后得到每一条边的存在时间之后,用线段树分治处理,使得只有加边的情况。

这个时候我们就用带撤销的并查集维护当前联通块的最长链。

答案就是当前点到最长链两端的较大值树剖+动态树+线段树分治+启发式合并并查集,总时间复杂度O(nlogn)。

代码长度7k+,常数极大。(考场A了2333)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
const int Maxn=200005;
inline int read() {
    char c; int rec=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
    return rec;
}
int n,m;
struct Edge {int a,b;} e[Maxn];
map< pair<int,int> , int > table;
namespace Graph {
    struct Branch {int next,to;} branch[Maxn<<1];
    int h[Maxn],cnt=0;
    inline void add(int x,int y) {
        branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
    }
    int size[Maxn],deep[Maxn];
    int top[Maxn],fa[Maxn],son[Maxn];
    void Dfs1(int v,int pre,int dep) {
        size[v]=1; fa[v]=pre; deep[v]=dep;
        for(int i=h[v];i;i=branch[i].next) {
            int j=branch[i].to;
            if(j==pre) continue;
            Dfs1(j,v,dep+1); size[v]+=size[j];
            if(size[son[v]]<size[j]) son[v]=j;
        } return ;
    }
    void Dfs2(int v,int T) {
        top[v]=T; if(son[v]) Dfs2(son[v],T);
        for(int i=h[v];i;i=branch[i].next) {
            int j=branch[i].to;
            if(j==fa[v]||j==son[v]) continue;
            Dfs2(j,j);
        } return ;
    }
    inline int Lca(int x,int y) {
        while(top[x]!=top[y]) 
            deep[top[x]]<deep[top[y]]?y=fa[top[y]]:x=fa[top[x]];
        return deep[x]<deep[y]?x:y;
    }
    inline int Dist(int x,int y) {
        return deep[x]+deep[y]-(deep[Lca(x,y)]<<1);
    }
}
namespace Lct{
    struct Splay {
        int F,s[2],size,rev;
        inline void NewNode(int fa) {
            F=fa; s[0]=s[1]=rev=0; size=1; return ;
        }
    }tree[Maxn];
    inline bool Isroot(int v){return tree[tree[v].F].s[0]!=v&&tree[tree[v].F].s[1]!=v;}
    inline void Pushup(int v){tree[v].size=tree[tree[v].s[0]].size+1+tree[tree[v].s[1]].size; return ;}
    inline void Rev(int v){if(v==0) return ; tree[v].rev^=1; swap(tree[v].s[0],tree[v].s[1]); return ;}
    inline void Pushdown(int v){if(tree[v].rev){Rev(tree[v].s[0]); Rev(tree[v].s[1]); tree[v].rev=0;} return ;}
    inline void Lazy(int v){if(!Isroot(v)) Lazy(tree[v].F); Pushdown(v); return ;}
    inline void Set(int v,int u,int f){tree[v].s[f]=u; tree[u].F=v; return ;}
    inline void Rotate(int v) {
        int p=tree[v].F,g=tree[p].F;
        int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
        if(!Isroot(p)) Set(g,v,t2); else tree[v].F=g;
        Set(v,p,!t1); Set(p,S,t1); Pushup(p); return ;
    }
    inline void Splay(int v) {
        for(Lazy(v);!Isroot(v);Rotate(v)) {
            int p=tree[v].F,g=tree[p].F;
            if(!Isroot(p)) Rotate((v==tree[p].s[1])^(p==tree[g].s[1])?v:p);
        } Pushup(v); return ;
    }
    inline void Access(int v){for(int u=0;v;u=v,v=tree[v].F){Splay(v); tree[v].s[1]=u; Pushup(v);} return ;}
    inline void Make_Root(int v){Access(v); Splay(v); Rev(v);}
    inline int Kth(int k,int v) {
        Pushdown(v);
        while(tree[tree[v].s[0]].size+1!=k&&v) {
            if(tree[tree[v].s[0]].size+1>k) v=tree[v].s[0];
            else k-=tree[tree[v].s[0]].size+1,v=tree[v].s[1];
            Pushdown(v);
        } return v;
    }
    inline pair<int,int> Get_Kth(int v,int k) {
        Access(v); Splay(v);
        if(tree[v].size<=k) return make_pair(0,0);
        int goal=tree[v].size-k;
        int u=Kth(goal,v);
        Splay(u); v=tree[u].s[1]; Pushdown(v);
        while(tree[v].s[0]) v=tree[v].s[0],Pushdown(v);
        if(u>v) swap(u,v);
        return make_pair(u,v);
    }
    inline void Build(int v,int pre) {
        tree[v].NewNode(pre);
        for(int i=Graph::h[v];i;i=Graph::branch[i].next) {
            int j=Graph::branch[i].to;
            if(j==pre) continue;
            Build(j,v);
        } return ;
    }
}
namespace Union {
    int fa[Maxn],size[Maxn];
    inline int getfa(int x){
        while(x!=fa[x]) x=fa[x];
        return x;
    }
    struct chain {
        int a,b,len;
    } A[Maxn];
    int top=0;
    struct node {
        int x,y,a,b,len;
    } S[Maxn];
    inline void reset() {
        for(int i=1;i<=n;i++)
            fa[i]=i,size[i]=1;
        for(int i=1;i<=n;i++)
            A[i].a=A[i].b=i,A[i].len=0;
        top=0; return ;
    }
    inline void Sov(int id) {
        int x=e[id].a,y=e[id].b;
        int fx=getfa(x),fy=getfa(y);
        if(size[fx]>size[fy]) swap(fx,fy);
        fa[fx]=fy; size[fy]+=size[fx];
        S[++top]=(node){fx,fy,A[fy].a,A[fy].b,A[fy].len};
        int a=A[fx].a,b=A[fx].b,c=A[fy].a,d=A[fy].b;
        int maxx=A[fx].len,temp; x=a; y=b;
        if(maxx<A[fy].len){maxx=A[fy].len; x=c; y=d;}
        temp=Graph::Dist(a,c); if(maxx<temp){maxx=temp; x=a; y=c;}
        temp=Graph::Dist(a,d); if(maxx<temp){maxx=temp; x=a; y=d;}
        temp=Graph::Dist(b,c); if(maxx<temp){maxx=temp; x=b; y=c;}
        temp=Graph::Dist(b,d); if(maxx<temp){maxx=temp; x=b; y=d;}
        A[fy].a=x; A[fy].b=y; A[fy].len=maxx;
        return ;
    }
    inline void Undo(int lim) {
        while(top>lim) {
            node temp=S[top--];
            int x=temp.x,y=temp.y;
            fa[x]=x; size[y]-=size[x];
            int a=temp.a,b=temp.b,c=temp.len;
            A[y].a=a; A[y].b=b; A[y].len=c;
        } return ;
    }
    inline int Ask(int x) {
        int fx=getfa(x);
        int a=A[fx].a,b=A[fx].b;
        int maxx=max(Graph::Dist(x,a),Graph::Dist(x,b));
        return maxx;
    }
}
int qpos[Maxn];
vector<int> broken[Maxn];
namespace Sgt {
    struct Segment_Tree {
        int L,R; vector<int> id;
    }tree[Maxn<<2];
    void Build(int v,int L,int R) {
        tree[v].L=L; tree[v].R=R;
        if(L==R) return ;
        int mid=(L+R)>>1;
        Build(v<<1,L,mid); Build(v<<1|1,mid+1,R);
        return ;
    }
    inline void Cover(int v,int L,int R,int x) {
        if(tree[v].L>R||tree[v].R<L) return ;
        if(L<=tree[v].L&&tree[v].R<=R) {
            tree[v].id.push_back(x); return ;
        }
        Cover(v<<1,L,R,x); Cover(v<<1|1,L,R,x); return ;
    }
    inline void Sov(int v) {
        int top=Union::top;
        for(int i=0;i<tree[v].id.size();++i)
            Union::Sov(tree[v].id[i]);
        if(tree[v].L==tree[v].R) {
            cout<<Union::Ask(qpos[tree[v].L])<<'\n';
            Union::Undo(top); return ;
        }
        Sov(v<<1); Sov(v<<1|1); Union::Undo(top);
        return ;
    }
}
int main() {
    n=read();
    for(int i=1;i<n;i++) {
        int x=read(),y=read();
        if(x>y) swap(x,y);
        e[i].a=x; e[i].b=y;
        Graph::add(x,y); Graph::add(y,x);
        table[make_pair(x,y)]=i;
    }
    table[make_pair(0,0)]=0;
    Graph::Dfs1(1,0,1); Graph::Dfs2(1,1); Lct::Build(1,0);
    m=read();
    int qcnt=0; char ch;
    for(int i=1;i<=m;i++) {
        while((ch=getchar())!='Q'&&ch!='C');
        if(ch=='Q') {
            int x=read(),k=read();
            ++qcnt; qpos[qcnt]=x;
            pair<int,int> temp=Lct::Get_Kth(x,k);
            int id=table[temp];
            if(id==0) continue;
            else broken[id].push_back(qcnt);
        }
        else {
            int x=read(); Lct::Make_Root(x);
        }
    }
    Sgt::Build(1,1,qcnt);
    for(int i=1;i<n;++i) {
        int L=1;
        for(int j=0;j<broken[i].size();++j) {
            Sgt::Cover(1,L,broken[i][j]-1,i);
            L=broken[i][j]+1;
        }
        Sgt::Cover(1,L,qcnt,i);
    }
    Union::reset();
    Sgt::Sov(1);
    return 0;
}

————————————————————我是日期的分割线———————————————————————

2018.8.1

隔了很久才考第二次啊2333

今天似乎是考了一套noip混合题目

T1是【NOIP 2007】字符串的展开

多读读题目,直接暴力模拟。

(说起来两年前做的适合只写了0.92k,这次就有2.46k了。。。)

主要是嫌麻烦的话就多写一点代码,也就是说会有复制粘贴一样的代码。

虽然很丑很长很难看,但是不是很容易错就对了。

T2 【NOIP 2005】篝火晚会

看上去像一个置换类的题目(有点想多了)

根据题意模拟出一种可行圆排列的展开。

然后找出对应位置最多的一种排列方式,再找出没有对应的个数。

(最后一个过程确实很像置换,但是只求总代价而没有涉及到置换次数,所以答案就是没有直接对位的个数了)

T3 【NOIP 2007】矩阵取数游戏

emm

显然的有一个似乎有点比较浅显的dp(似乎这行很难读?2333)

我们发现两行之间是没有影响的,把每一行的最大值相加就能够直接得到全局的最大值。

dp方程:

定义NOIp系列题目及CF小结代表从第i个取到第j个的最高价值。

那么,显然有NOIp系列题目及CF小结

Base就是那个啥,就是当前的2^i次方。

然后初始值就是NOIp系列题目及CF小结

就是高精度(2^80次方你怕不怕)有点烦(__int128好像也行)

存一下高精度的部分

const long long mod=100000000;
struct Big_number {
	long long a[10]; int top;
	Big_number(){memset(a,0,sizeof(a)); top=1; return ;}
	inline void operator = (const long long x) {a[1]=x; top=1; return ;}
	inline void Print() {
		int x=top;
		cout<<a[x--];
		while(x) printf("%08lld",a[x--]);
		return ;
	}
	
}A,B,C;
inline Big_number operator * (const Big_number &x,const Big_number &y){
	Big_number rec;
	for(int i=1;i<=x.top;++i)
		for(int j=1;j<=y.top;++j)
			rec.a[i+j-1]+=x.a[i]*y.a[j];
	rec.top=x.top+y.top-1;
	for(int i=1;i<=rec.top;++i)
		rec.a[i+1]+=rec.a[i]/mod,rec.a[i]%=mod;
	while(rec.a[rec.top+1]) ++rec.top;
	return rec;
}
inline Big_number operator + (const Big_number &x,const Big_number &y) {
	Big_number rec;
	int lim=max(x.top,y.top);
	for(int i=1;i<=lim;++i) {
		rec.a[i]+=x.a[i]+y.a[i];
		rec.a[i+1]+=rec.a[i]/mod;
		rec.a[i]%=mod;
	}
	rec.top=lim;
	if(rec.a[rec.top+1]) ++rec.top;
	return rec;
}
inline bool operator > (const Big_number &x,const Big_number &y) {
	if(x.top>y.top) return 1;
	else if(x.top<y.top) return 0;
	for(int i=x.top;i;--i)
		if(x.a[i]>y.a[i]) return 1;
		else if(x.a[i]<y.a[i]) return 0;
	return 0;
}

感觉还行。。。

dp还是贴一些吧(CSDN代码片不能折叠,绝望。)

long long ans=0;
for(int i=1;i<=n;++i) {
	long long base=1ll<<m;
	for(int k=1;k<=m;++k)
		f[k][k]=map[i][k]*base;
	for(int len=2;len<=m;++len) {
		base>>=1ll;
		for(int j=1;j+len-1<=m;++j)
			f[j][j+len-1]=max(f[j][j+len-2]+map[i][j+len-1]*base,f[j+1][j+len-1]+map[i][j]*base);
	}
	ans+=f[1][m];
}
cout<<ans;

T4 【NOIP 2008】双栈排序

经典的老题目了。

首先二分图匹配一下来找找合法性。(一个数字肯定是要进一个栈,对吧)(没错)

然后就在图上找字典序最小的输出方案就是了。

emm,没有代码。(网上辣么多)

————————————————————我是日期的分割线———————————————————————

2018.8.2

今天是由高三毕业的ZY学长带来的模拟题。

(我有点毒瘤啊。。。3道题一道没A总分上了250【果然吗】

T1

简明题意:有A,B,C三种物品。物品的代价分别为2,5,1,总收益为A+B*C。

在总收益为n的情况下,使得代价最小。

若有多个,则优先使B最小。

n<=10^14

*******************************

考场上感觉有点复杂,所以直接做了一个n^2的dp开始打表,找到了一些规律,但实际上并不用这么麻烦。

显然,对于总收益贡献更大的部分是后边的B*C,我们使得它最大化是能够得到最优解的。

然后B的代价更大,所以B的大小一定不会超过√n。

直接枚举B,得到最大的C(n/b),然后剩下的就是A了。

取一下最小值。这样问题的规模就是10^7,O(√n)的算法就可以解决问题了。

(考试的时候打表的出来的规律计算时间复杂度也是O(√n),但是会多一些乘除法的运算。所以T成90分)

T2

简明题意:你有n堆石子,每次任选k堆,从这k堆中分别取出一颗石子。当总的石子堆数不足k时结束,最大化取石子次数。

其中,k<n<=10^5,ai(个数)<=10^9

显然,我们每一次都取最大的k堆一定能够得到最优解,所以n^2的算法就很显然了。

(然后加上奇奇怪怪骗来的30分一共有80分了)

正解:二分

说来这个二分非常的神奇。

我们直接二分答案(答案的范围在【0,10^14】),就是取石子的次数。

分别对每一堆石子考虑:

如果石子个数大于取石子的次数m,那么显然每一次都可以取这个石子,也就是说,这一堆石子的贡献最大为m。

否则,这一堆石子可能被全部取完,则贡献最大为a[i]。

要使得这个次数m满足要求,则总共取出的石子要大于等于m*k。

那么我们就可以在O(n)的时间内完成检测了。

T3

可以说是最常见的题目了。

给定一棵树,询问节点x子树中前k大节点的权值和。

由于时限两秒,n<=10^5,不用在线,所以有很多奇怪的解法。

首先可以直接莫队+Splay(真有人这样写),O(n√nlogn),能够拿80分(卡卡常,松一松说不定能过)

然后还是莫队+权值分块,O(n√n)稳过。

接着就是启发式合并的权值线段树/Splay,O(nlog^2n),标解给的就是这个。

显然可以考虑离线+线段树合并,时空复杂度均为O(nlogn)。

当然,Dfs序展开之后直接上主席树也可以O(nlogn)。

我写的启发式+Splay,由于有100000的单链数据所以爆栈了两组(80分)。

大概就是这样(我可能是全场唯一一个一道题都没有A的蒟蒻了)。

————————————————————我是日期的分割线———————————————————————

2018.8.3

ZY模拟赛第二套

T1

只有三行的O(n)dp。(其实该放在昨天第一题)

T2

给定一个n*m的矩形,用2*2的方块来覆盖,每一个格子都有权值,求最大能够得到的权值。

n,m<=11

非常显然的状压Dp,有效状态只有144个。

也就是说时间复杂度最大就是O(11*144*144)【标解居然是爆搜】

(事实上这个题是一个简化版本,原来的要求是用任意个大小互不相同的正方形来覆盖,这样的话就只能够爆搜了Orz)

T3

n个点的完全图,两个点之间的权值为点权的异或值。

求最小生成树的权值。

一行五个整数,n,v[1],A,B,P,其中 n 表示点的数量,v[1] 表示点 1 的权值。 对于 2≤i≤n,v[i]=(v[i-1]*A+B)%P。

对于n<=10^5,没有特殊约定

对于n<=10^9,有v[1]=0,A=1,B=1.

————————————————————我是日期的分割线———————————————————————

2018.8.4

今天又是4道noip老题。

T1【NOIP 2009】潜伏者

模拟

T2【NOIP 2009】Hankson的趣味题

数学分析一下之后直接√n枚举过去

T3【NOIP 2009】最优贸易

我们发现答案只和路径上的最大和最小值有关。

然后我们可以直接两次Spfa,第一次从1号点去更新最小值。

然后将有向图的边反向,第二次从n号点去更新最大值。

然后答案就是同一个点的最大最小值之差的最大值

T4【NOIP 2006】2^k进制数

emmm。简单发现显然和组合数相关,然后上高精度做一下就好了。

(emm,根本不会卡空间啊)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Mod=1000000;
int n,k;
int a[105],b[105];
inline void Print(int *a) {
	cout<<a[a[0]];
	for(int i=a[0]-1;i>=1;i--)
		printf("%06d",a[i]);
	return ;
}
inline void Mul(int *a,int b) {
    for(int i=1;i<=a[0];++i) a[i]*=b;
    for(int i=1;i<=a[0];++i)
       a[i+1]+=a[i]/Mod,a[i]%=Mod;
    while(a[a[0]+1])
    	a[a[0]+1]+=a[a[0]]/Mod,a[a[0]]%=Mod,++a[0];
    return ;
}
inline void Add(int *a,int *b) {
   a[0]=max(a[0],b[0]);
   for(int i=1;i<=a[0];i++) {
	  a[i]+=b[i];
	  a[i+1]+=a[i]/Mod;
	  a[i]%=Mod;
   }
   if(a[a[0]+1]) ++a[0];
   return ;
}
inline void Div(int *a,int b) {
    int x=0;
    for(int i=a[0];i>=1;i--) {
	   x=x*Mod+a[i];
       a[i]=x/b; x%=b;
	}
    while(a[0]&&(a[a[0]]==0)) --a[0];
    return ;
} 
inline void Cal(int *a,int m,int n) {
	a[0]=1; a[1]=1;
	if(n>m){a[1]=0; return ;}
	if(m==n){a[1]=1; return ;}
	for(int i=1;i<=n;++i) {
		Mul(a,m-i+1); Div(a,i);
	}
	return ;
}
int main(){
	b[0]=1;b[1]=0;
	cin>>n>>k;
	int len=k/n;
	if(k<=n){cout<<0;return 0;}
	int x=(1<<n)-1;
	for(int i=2;i<=len;i++) {
		Cal(a,x,i); Add(b,a);
		memset(a,0,sizeof(a));
	}
	if(k>len*n){
		int p=k-len*n;
		p=(1<<p)-1;
		for(int i=1;i<=p;i++){
			Cal(a,x-i,len); Add(b,a);
			memset(a,0,sizeof(a));
		}
	}
	Print(b);
	return 0;
}

————————————————————我是日期的分割线———————————————————————

2018.8.5

emm四道老题

T1【NOIP 2011】计算系数

直接杨辉三角递推(都知道二项式定理吧)

T2【NOIP 2011】选择客栈

我们发现k很小,所以就依据k来进行O(n)的递推

T3【NOIP 2010】关押罪犯

要使得剩下两部分的边权最大值最小,那么被断开的边权就要尽量的大。

所以就是把原图分成一张二分图,且使得这个二分图匹配的最小值最大。

二分+二分图判定。

听说有用并查集维护的方式,以后再看看。

T4【NOIP 2006】2^k进制数

emm手推一下求和的柿子。

然后写一个高精度就行了(居然有高精度除以单精度)

————————————————————我是日期的分割线———————————————————————

2018.8.6

今天还是四道题,但是不是老题了。

T1

Description

  在直角坐标系上,有N个边平行于坐标轴的矩形。你的任务是把其中的K个矩形染色,使按次序放下后,可以看见的有色面积最大。可看见的意思就是这一部分没有被后面的矩形覆盖。
  你的答案是返回K个整数,表示你染色的是哪K个矩形。如果有多种答案,输出字典序最小的。

Input

  第一行两个整数:N K
  后面有N行,每行4个整数: x1 y1 x2 y2, 分别表示先后各个矩形的左下角坐标和右上角坐标。

Output

  一行,K个整数:你的方案。

Sample Input

3 2
1 1 5 3
3 2 7 4
2 5 9 7

Sample Output

1 2

数据范围

1<=N<=50; 1<=K<=N。
每个坐标值为[-10000,10000]之间的整数。

 

数据范围很小啊。

显然后来的矩形会覆盖之前的矩形,之前的矩形不会影响之后的矩形。

所以我们倒序算出每一个矩形的面积,选出最大的K个即可。

怎样算出面积呢?

由于数据过小,甚至可以暴力容斥。(我是不会的)

矩形面积可以用扫描线法。

由于坐标范围小,矩形个数少,所以可以暴力枚举扫描线。

(当然是可以使用线段树维护的。。。)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read() {
	static char c;
	int rec=0,f=0;
	while((c=getchar())<'0'||c>'9') f|=c=='-';
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
	return f?-rec:rec;
}
int n,K;
struct Squar {int x1,y1,x2,y2;} a[55];
int v[20005];
int ans[55],Y[105],X[105];
struct Mat {int id,S;} e[55];
inline bool operator < (const Mat &A,const Mat &B) {return A.S>B.S||A.S==B.S&&A.id<B.id;}
int main() {
	n=read(); K=read();
	for(int i=1;i<=n;++i) {
		a[i]=(Squar){read(),read(),read(),read()};
		e[i].id=i,e[i].S=0;
		X[(i<<1)-1]=a[i].x1; X[i<<1]=a[i].x2;
	}
	sort(X+1,X+(n<<1)+1);
	for(int i=1;i<=n<<1;++i)
		if(X[i]!=X[i-1])
			Y[++Y[0]]=X[i];
	for(int i=1;i<=Y[0]-1;++i) {
		memset(v,0,sizeof(v));
		for(int j=n;j;--j) {
			int sum=0;
			if(a[j].x1<=Y[i]&&a[j].x2>=Y[i+1]) {
				for(int pos=a[j].y1+1;pos<=a[j].y2;++pos)
					if(v[10000+pos]==0) {
						++sum;
						v[10000+pos]=1;
					}
				e[j].S+=sum*(Y[i+1]-Y[i]);
			}
		}
	}
	sort(e+1,e+n+1);
	for(int i=1;i<=K;++i) ans[i]=e[i].id;
	sort(ans+1,ans+K+1);
	for(int i=1;i<=K;++i) cout<<ans[i]-1<<" ";
	return 0;
}

T2

Description

对于排列 ( P 1 , P 2 ,..., P N ) ,定义 ( i , j ) 为逆序对当且仅当 i < j 且 P i > P j 。统计{1,2,..., N } 的所有排列中,逆序对数量为 M 的排列数量。

Input

第一行包含两个正整数 N , M 。

Output

应包含一个整数 , 表示满足条件的排列数除以 124567 的余数。

Sample Input

3 1

Sample Output

2

Hint

【数据规模和约定】
30%的数据 , N ≤ 10 ;
100%的数据 , 0 < N , M ≤ 1000 。

【【【坑点:模数是124567,(3?,不存在的)】】】

我们考虑一下dp。

n^2的dp是可以接受的。

定义f(i,j)为前i个数组成了j个逆序对的排列方案数

对于新加入的第n个数,由于之前的数都比他小,所以把它放在第k个位置会增加n-k个逆序对。

这样我们就得到了一个n^3的dp(卡卡说不定能过?)

我们定义一下前缀和来优化就好了

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int dp[1005][1005];
int n,m;
int main(){
	cin>>n>>m;
	int i,j,k;
	for(i=0;i<=n;i++)dp[i][0]=1;
	for(i=1;i<=n;i++){
		for(j=1;j<=min(m,i*(i-1)/2);j++){
			dp[i][j]=(dp[i][j-1]+dp[i-1][j])%124567;
			if(j>=i)dp[i][j]=(dp[i][j]-dp[i-1][j-i]+124567)%124567;
 
		}
	}
	cout<<dp[n][m];
	return 0;
}

T3

【问题描述】

    小PP因为整天思考如何摆放农田的问题,每天晚上都失眠,于是小PP每天晚上都在数羊。

对于每个羊i,都有一个吵闹程度a[i],每个羊的吵闹程度都不同。

小PP要数的是对于羊i,j,k(i<j<k)满足(a[i]<a[k])而且(a[k]<a[j])的羊的3 元排列(i,j,k)组数。但是数到一半,小PP发现羊太多了,于是大吼一声:“你们别吵啦!”。

现在小PP想请你帮他数这样的羊的组数。

【输出】

输入的第一行有一个正整数N,即羊的总个数。

接下来一行有N个不同的正整数,第i个数表示第i 头羊的吵闹程度。

【输出】

    输出有且仅有一个整数,即要求的羊的组数。

【输入样例】

3

1 3 2

【输出样例】1

【数据规模】

    对于20%的数据,有N<=100;

    对于40%的数据,有N<=1000;

    对于60%的数据,有N<=100000;

10w的数据。。。

显然只能够nlogn了。

其实暴力数据结构一波就行,但是似乎需要树套树log^2。

所以我们运用一下补集转换的思想(由于是一个1~n的排列)

我当时脑残用了主席书,其实树状数组就行了。

贴一下考场的代码算了

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=200005;
inline int read() {
	static char c; int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,a[Maxn];
#define mid ((L+R)>>1)
int root[Maxn],ind=0;
struct Persistent_Segment_Tree {int s[2],d;} tree[Maxn*30];
inline void Infix(int &v,int p,int L,int R,int x) {
	v=++ind; tree[v]=tree[p]; ++tree[v].d;
	if(L==R) return ;
	int f=(x>mid); f?L=mid+1:R=mid;
	Infix(tree[v].s[f],tree[p].s[f],L,R,x);
	return ;
}
inline int Lower(int v,int L,int R,int lim) {
	if(L>lim) return 0;
	if(R<=lim) return tree[v].d;
	return Lower(tree[v].s[0],L,mid,lim)+Lower(tree[v].s[1],mid+1,R,lim);
}
inline int Upper(int v,int L,int R,int lim) {
	if(R<lim) return 0;
	if(L>=lim) return tree[v].d;
	return Upper(tree[v].s[0],L,mid,lim)+Upper(tree[v].s[1],mid+1,R,lim);
}
int main() {
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) Infix(root[i],root[i-1],1,n,a[i]);
	long long ans=0;
	for(int i=1;i<=n;++i) {
		long long x=Lower(root[i],1,n,a[i]-1);
		long long y=n-a[i]-(Lower(root[i],1,n,n)-Lower(root[i],1,n,a[i]));
		ans+=y*(y-1-2*x)/2;;
	}
	cout<<ans;
	return 0;
}

T4

题面极长而且读不懂导致考场当场爆零

。。。

简化版题面:

给定一张无向图,要把这张无向图分为两个部分,分割的代价是断边中权值最大的一条。

现在有 Q次询问,每次给定两个点x、y,要求这两个点不能够分在同一侧,求最小的分割代价。

求出最大生成树之后输出x,y两点之间的最小边即可。

具体做法可以使用Kruskal重构树获得更加优越的时空复杂度以及代码复杂度。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=50005,Maxm=100005;
inline char C(){
    static char buf[1<<20],*S=buf,*T=S;
    return S==T&&(T=(S=buf)+fread(buf,1,1<<20,stdin),S==T)?EOF:*S++;
}
inline int read() {
	static char c; int rec=0;
	while((c=C())<'0'||c>'9');
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=C();
	return rec;
}
int n,m,Q,mod;
struct Edge {int a,b,w;} e[Maxm];
inline bool operator < (const Edge &A,const Edge &B) {return A.w>B.w;}
struct Branch {int next,to;} branch[Maxn<<1];
int h[Maxn<<1],cnt=0;
inline void add(int x,int y) {
    branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
}
int fa[Maxn<<1],val[Maxn<<1];
inline int getfa(int x) {return x==fa[x]?x:fa[x]=getfa(fa[x]);}
inline void Ex_Calibur() {
    int ind=n,lim=n<<1; sort(e+1,e+1+m);
    for(int i=1;i<=lim;++i) fa[i]=i;
    for(int i=1;i<=m;++i) {
        int fx=getfa(e[i].a),fy=getfa(e[i].b);
        if(fx!=fy) {
            fa[fx]=fa[fy]=++ind;
            val[ind]=e[i].w;
            add(ind,fx); add(ind,fy);
            if(ind==lim-1) break;
        }
    } return ;
}
int size[Maxn<<1],deep[Maxn<<1];
int top[Maxn<<1],son[Maxn<<1];
inline void Dfs1(int v,int pre,int dep) {
    size[v]=1; fa[v]=pre; deep[v]=dep;
    for(int i=h[v];i;i=branch[i].next) {
        int j=branch[i].to;
        Dfs1(j,v,dep+1); size[v]+=size[j];
        if(size[son[v]]<size[j]) son[v]=j;
    } return ;
}
inline void Dfs2(int v,int T) {
    top[v]=T; if(son[v]) Dfs2(son[v],T);
    for(int i=h[v];i;i=branch[i].next) {
        int j=branch[i].to;
        if(j^fa[v]&&j^son[v]) Dfs2(j,j);
    } return ;
}
inline int Ask(int x,int y) {
    while(top[x]!=top[y]) deep[top[x]]<deep[top[y]]?y=fa[top[y]]:x=fa[top[x]];
    return deep[x]<deep[y]?val[x]:val[y];
}
int main() {
	n=read(); m=read(); Q=read(); mod=read();
	for(int i=1;i<=m;++i)
		e[i]=(Edge){read(),read(),read()};
	Ex_Calibur();//突然...
    Dfs1((n<<1)-1,0,1); Dfs2((n<<1)-1,(n<<1)-1);
	int ans=1;
	for(int i=1;i<=Q;++i) {
		int x=read(),y=read();
		ans=(1ll*ans*Ask(x,y))%mod;
	}
	cout<<ans;
	return 0;
}

————————————————————我是日期的分割线———————————————————————

2018.8.7

三道新题

T1 

奇怪排序

【问题描述】  

 小明写了如下代码来完成排序,他的代码现在是这样的:

------------------

sorted = false

while (not sorted):

   sorted = true

   moo

   for i = 0 to N-2:

      if A[i+1] < A[i]:

         swap A[i], A[i+1]

   for i = N-2 downto 0:

      if A[i+1] < A[i]:

         swap A[i], A[i+1]

   for i = 0 to N-2:

      if A[i+1] < A[i]:

         sorted = false

----------------------

moo 是一个输出语句,代码为缩进风格。给定一个输入数组,请预测该代码会输出多少次“moo”。

【输入格式】

  输入的第一行包含N(1≤N≤100,000)。接下来N行描述了A[0]…A[N-1],每个数都是一个范围为0…10^9的整数。输入数据不保证各不相同。

【输出格式】

  输出“moo”被输出的次数。

【样例输入1

5

1

8

5

3

2

【样例输出1

2

这个"moo",额,奶牛?USACO?

先不管这些,这道题看起来是一道智商题目。

我们读一下伪代码,发现好像是一个像冒泡排序的东西。(考场确实没有看出来)

显然,每一次操作都会至少把最小的数放在最前面,最大的数放在最后面。

对于一个数,如果有比它大的数在它的左边,那么一定会被交换一次。

如果只有简单的单向冒泡的话,那么答案就是所有数的逆序对中最大的一个,但是由于这段伪代码是双向的冒泡,所以有可能当前数自己去和前面的数交换。

我们把这些数字记下原有位置之后按照值的大小排序,然后由小到大判断逆序对的个数。

对于已经被访问过的数(就是有数已经略过了它),那么逆序个数减1

(其实还是不太懂)

#include<bits/stdc++.h>
using namespace std;
inline int read() {
	static char c; int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,vis[100005],ans=1,cnt=0;
struct node {int x,id;} a[100005];
inline bool operator < (const node &A,const node &B) {return A.x<B.x||A.x==B.x&&A.id<B.id;}
int main() {
	n=read();
	for(int i=1;i<=n;++i)
		a[i]=(node){read(),i};
	sort(a+1,a+1+n);
	for(int i=1;i<=n;++i) {
		if(a[i].id>i) ++cnt;
		if(vis[i]) --cnt;
		ans=max(ans,cnt);
		vis[a[i].id]=1;
	} cout<<ans;
	return 0;
}

T2

观察顺序

【问题描述】

 有一个编号为1…N(1≤N≤10^5)的排列,小明对数列进行了M次观察(1≤M≤50,000)。每个观察结果都是某些数的一个有序序列,表示这些数出现的顺序。比方说,如果小明的一次观察结果是序列2、5、1,则2在5的前面,5在1的前面。小明的观察结果是按优先级排列的,所以他的目标是最大化X的值,使得最终完整排列顺序能够符合前X个观察结果描述的状态。当多种排列顺序都能符合前X个状态时,小明会让编号较小的数放前面。请帮助小明求出该排列。

 

【输入格式】

第一行包含N和M。

接下来的M行,每行描述了一个观察结果。

第i+1行描述了观察结果i,第一个数是观察结果中的数的数量mi,后面是一列mi个整数,给出这次观察中的顺序。

所有mi的和至多为200,000

【输出格式】

输出N个空格分隔的整数,给出一个1…N的排列,为小明求出的该排列。

【样例输入】

4 3

3 1 2 3
2 4 2
3 3 4 1

【样例输出】

1 4 2 3

显然,我们可以认为形如 “A B” 的顺序意味着A>B。

这是一个单向的关系,对于一个合法的排列,一定不存在 A>B 且 B>A 的情况。

所以我们直接连一张有向图,判定是否为DAG即可。

简单来说,就是二分+Topsort。(字典序注意使用优先队列)

考试的时候犯傻用了Tarjan检验DAG(时间复杂度倒是没差)

T3

 

选数

【问题描述】

    小明有N个数,方便起见编号为1…N,每个数俩个属性。他的第i个数属性1为wi,属性2为ti,两者都是整数。选数有如下规则:

(一)选出的一组数属性1之和至少为W

并且

(二)属性2总值与属性1总值的比值最大。

小明的有N个数属性1总值不小于W,所以他能够选出符合规则(一)的数。

 

【输入】

输入的第一行包含N和W。下面N行,每行用两个整数wi和ti描述了一个数。

1≤N≤250,1≤W≤1000,1≤wi≤10^6,1≤ti≤10^3

 

【输出】

    请求出小明选数的最大可能达到的比值。如果你的答案是A,输出1000A向下取整的值,以使得输出是整数(当问题中的数不是一个整数的时候,向下取整操作在向下舍入到整数的时候去除所有小数部分)。

【输入样例】

3 15
20 21
10 11
30 31

【输出样例】

1066

emmm考试的时候居然不知道什么是01分数规划(什么时候漏掉的知识点?)

好吧。

模板题啦,二分最值之后O(nw)dp判定

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const double eps=1e-9;
inline int read() {
	static char c; int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,W;
int w[305],t[305];
double f[1005];
inline bool Check(double lim) {
	for(int i=1;i<=W;i++) f[i]=-1e9;
	for(int i=1;i<=n;i++){
		double temp=(double)t[i]-(double)w[i]*lim;
		for(int j=W;j>=W-w[i]&&~j;j--){
			f[W]=max(f[W],f[j]+temp);
		}
		for(int j=W-1;j>=w[i];j--){
			f[j]=max(f[j],f[j-w[i]]+temp);
		}
	}
	return f[W]>=0;
}
int main() {
	n=read(); W=read();
	for(int i=1;i<=n;++i) w[i]=read(),t[i]=read();
	double L=0,R=1000000,mid;
	while(L+eps<R) {
		mid=(L+R)/2;
		if(Check(mid)) L=mid;
		else R=mid;
	}
	cout<<int(L*1000);
	return 0;
}

————————————————————我是日期的分割线———————————————————————

2018.8.8

今天又回到了NOIp老题四连

T1【NOIP 2011】铺地毯

并不想说话。。。倒序判定即可

T2NOIP 2011】聪明的质检员

我们发。。。。

算了,就是二分+判定,就是在二分的过程中注意一下更新答案即可。

T3【NOIP 2011】Mayan游戏

哇塞,noip特产之爆搜题。

贴一下程序。注意一个小优化(看注释啦)

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
int n;
int map[10][10];
struct ANS {int x,y,opt;} a[5];
inline bool Down() {
	int F=0;
	for(int i=1;i<=5;++i) {
		for(int j=2;j<=7;++j) {
			if(map[i][j]) {
				int x=i,y=j;
				while(map[x][y-1]==0&&y>1) {
					F=1;
					swap(map[x][y-1],map[x][y]);
					--y;
				}
			}		
		}
	} return F;
}
int flag[10][10];
inline void Clear() {
	for(int i=1;i<=5;++i) {
		for(int j=1;j<=7;++j) {
			if(map[i][j]) {
				int col=map[i][j];
				if(i+2<=5) {
					int x=i;
					while(x+1<=5&&map[x+1][j]==col)
						++x;
					if(x-i>=2)
						for(int t=i;t<=x;++t)
							flag[t][j]=1;
				}
				if(j+2<=7) {
					int y=j;
					while(y+1<=7&&map[i][y+1]==col)
						++y;
					if(y-j>=2)
						for(int t=j;t<=y;++t)
							flag[i][t]=1;
				}
			}
		}
	}
	for(int i=1;i<=5;++i)
		for(int j=1;j<=7;++j)
			if(flag[i][j]) 
				map[i][j]=flag[i][j]=0;
	return ;
}
inline bool Cleaned() {
	for(int i=1;i<=7;++i)
		for(int j=1;j<=5;++j)
			if(map[i][j]) return 0;
	return 1;
}
int FFLLAAGG=0;
void Dfs(int step) {
	if(step==n) {
		if(Cleaned()) {
			for(int i=0;i<n;++i)
				cout<<a[i].x<<" "<<a[i].y<<" "<<a[i].opt<<'\n';
			exit(0);
		}
		return ;
	}
	int temp[10][10];
	memcpy(temp,map,sizeof(map));
	for(int i=1;i<=5;++i) {
		for(int j=1;j<=7;++j) {
			if(map[i][j]) {
				if(i<5) {
					a[step].x=i-1; a[step].y=j-1; a[step].opt=1;
					swap(map[i][j],map[i+1][j]);
					Down();
					do {
						Clear();
					} while(Down());
					Dfs(step+1);
					memcpy(map,temp,sizeof(map));
				}
				if(i>1&&!map[i-1][j]) { 
                //由于优先向右移,所以如果左边位置不是空格的话一定会先被向右的置换搜索到
					a[step].x=i-1; a[step].y=j-1; a[step].opt=-1;
					swap(map[i][j],map[i-1][j]);
					Down();
					do {
						Clear();
					} while(Down());
					Dfs(step+1);
					memcpy(map,temp,sizeof(map));
				}
			}
		}
	}
	return ;
}
int main() {
	cin>>n;
	for(int i=1;i<=5;++i) {
		int x,cnt=0;
		while(1) {
			cin>>x;
			if(x==0) break;
			map[i][++cnt]=x;
		}
	}	
	Dfs(0);
	cout<<-1;
	return 0;
}

T4【NOIP 2011】观光公交

我们考虑一下没有N2的情况。

显然,汽车到达每一个点的时间t[i]=max(t[i-1],last[i-1])+d[i-1]

其中,last是到达第i个站点最晚的乘客的到达时间,d是两站之间的距离

如果有一段距离变短了会怎么样呢?

假设到第i个站点的距离减少了1,那么到达第i站的时间会减少1,到达第i+1站的时间也会减少1,以此类推。

《《《》》》

等等,好像有什么问题。

我们确实能够提前到达第i站,但是是否能够提前离开呢?

这就不一定吧。

所以我们再处理出来每一段路能够影响的最远的站点,那么如果在这一段路上使用了N2就会使得所有终点站在这一区间的乘客总等待时间减一。

显然,两次N2的使用是相互独立的,所以我们可以贪心的来取最大值,计算出人数的前缀之后查分是计算的好方法。

注意取值的时候不能取到距离为负,取完之后记得更新距离和到达时间。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=1005,Maxm=10005;
inline int read() {
	static char c; int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,m,K,ans=0;
int d[Maxn],lastpsn[Maxn],t[Maxn],pcnt[Maxn],dic[Maxn];
struct Psn {int t,a,b;} a[Maxm];
int main() {
	n=read(); m=read(); K=read();
	for(int i=1;i<n;++i) d[i]=read();
	for(int i=1;i<=m;++i)
		a[i]=(Psn){read(),read(),read()};
	for(int i=1;i<=m;++i) {
		lastpsn[a[i].a]=max(lastpsn[a[i].a],a[i].t);
		++pcnt[a[i].b];
	}
	for(int i=1;i<=n;++i) pcnt[i]+=pcnt[i-1];
	t[1]=0;
	for(int i=2;i<=n;++i)
		t[i]=max(t[i-1],lastpsn[i-1])+d[i-1];;
	for(int i=1;i<=m;++i)
		ans+=t[a[i].b]-a[i].t;
	while(K--) {
		dic[n-1]=n;
		for(int i=n-2;i;--i)
			if(t[i+1]>lastpsn[i+1])
				dic[i]=dic[i+1];
			else dic[i]=i+1;
		int maxx=0,id;
		for(int i=1;i<n;++i)
			if(pcnt[dic[i]]-pcnt[i]>maxx&&d[i]>0)
				maxx=pcnt[dic[i]]-pcnt[i],id=i;
		--d[id]; ans-=maxx;
		for(int i=id+1;i<=n;++i)
			t[i]=max(t[i-1],lastpsn[i-1])+d[i-1];
	}
	cout<<ans;
	return 0;
}

 

相关标签: noip 模拟赛