NOIp系列题目及CF小结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hwzzyr/article/details/80885292

长期更新中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方程:

定义代表从第i个取到第j个的最高价值。

那么,显然有

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

然后初始值就是

就是高精度(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;
}

猜你喜欢

转载自blog.csdn.net/hwzzyr/article/details/80885292
今日推荐