20170820四校联考

来看看IOIAu巨神zzx的名言

不用循环输入就会狗啊哥哥!

上题目:

T1:

填算式(expr)

【题目描述】

填算式是一种简单的数学游戏,可以形式化描述如下:n 个数字a1; a2; …… ; an 排成一排(1<= ai<=9),相邻两个数字之间有一个空格。你可以在每个空格内填入运算符+- * 之一,也可以不填,要求得到的算式的运算结果等于k。你的任务是计算有多少种不同的正确算式。比如n = 3,3 个数字为2; 2; 2,k = 24时,有两种不同的正确算式:22 + 2 = 24,2 + 22 = 24。

【输入格式】
从文件expr.in 中读入数据。
输入的第一行包含两个整数n; k,表示数字个数和要求的答案。
接下来一行,包含n 个整数,第i 个数为ai。相邻两个整数用一个空格隔开。
【输出格式】
输出到文件expr.out 中。
输出一个整数,表示不同的正确算式个数。
【样例1 输入】
4 11
1 2 3 4
【样例1 输出】
3
【样例1 解释】
3 个正确的算式为:12 + 3 􀀀 4 = 11,1 + 2  3 + 4 = 11,1 􀀀 2 + 3  4 = 11。
【样例2 输入】
7 1
1 1 1 1 1 1 1
【样例2 输出】
241

【样例3 输入】
10 3276
7 7 8 6 1 4 1 1 1 4
【样例3 输出】
104

【子任务】
子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只
解决一部分测试数据。
每个测试点的数据规模及特点如下表:


题解:

比较简单的搜索吧(虽然我打挂了)
直接DFS从左到右枚举每个空格填+,-,×还是不填,然后计算这个式子的值,这样复杂度是4^(n-1)*n,期望得分95分

在DFS的参数里面存储当前运算结果,可以一边DFS一边计算,具体做法如下:
维护参数 a,b,c,初始 a=c=0,b=1
不填,则 c->10c+x[i]
填+,则 a->a+bc,b=1,c=x[i]
填-,则 a->a+bc,b=-1,c=x[i]
填×,则 b->bc,c=x[i]
最后 a+bc就是运算结果
复杂度优化到4^(n-1),期望得分100分


部分分算法:
如果不会DFS,可以用三重循环通过测试点1~12,期望得分60分

如果不会循环,可以用条件语句通过测试点1~4或者1~8,期望得分20~40分

代码:

#include<cstdio>
#define r register
#define Fn "expr"
typedef long long ll;
int n,k,ans,x[15];
bool check(ll a,ll b,ll c){return a+b*c==k;}
void dfs(int st,ll a,ll b,ll c){
    if(st==n){ans+=check(a,b,c);return;}
    dfs(st+1,a,b,c*10+x[st]);
    dfs(st+1,a+b*c,1,x[st]);
    dfs(st+1,a+b*c,-1,x[st]);
    dfs(st+1,a,b*c,x[st]);
}
int main(){
    freopen(Fn".in","r",stdin);
    freopen(Fn".out","w",stdout);
    scanf("%d%d",&n,&k);
    for(r int i=0;i<n;i++)scanf("%d",x+i);
    dfs(1,0,1,x[0]);
    printf("%d\n",ans);
    return 0;
}
View Code

T2:

闭合子图(closure)


【样例1 输入】
5 5
1 3
3 4
5 4
1 5
2 1
【样例1 输出】
5


题解:

 

部分分算法:枚举子集
枚举 V 的非空子集 S,根据定义判定是否满足条件,更新答案
判定部分实现优秀的话可以做到O(1),期望得分20~25分


部分分算法:枚举区间
注意到点集构成的区间只有 n(n+1)/2 个,可以枚举区间然后将区间内的点作为S,进行判定
直接实现的复杂度 O(n^2*m),期望得分35分

注意到对于一个点u的出边(u,v),只有最小的v和最大的v是有用的,可以将m缩小到2n
复杂度 O(n^3),期望得分40分

进一步地,可以枚举左端点,然后从小到大枚举右端点,维护区间内出边指向点的最小值和最大值
复杂度 O(n^2),期望得分50分


部分分算法:树的数据

对于测试点11,12,显然所有形如[1,i]的区间都满足条件
所以答案为n,复杂度O(n),期望得分10分,结合上述算法期望得分60分


部分分算法:DAG的数据

对于测试点13,14,判定区间[l,r]是否满足要求只需要[l,r]内的点指向的点编号均不超过r
用p[i]表示i指向的点的编号(不存在则p[i]=i),则可以枚举r,找出第一个满足p[i]>r的i,那么l=i+1,i+2,...,r都是合法的,答案加上r-i
从左到右维护p[i]的递减单调栈即可,复杂度O(n),期望得分15分。
结合上述算法期望得分75分


算法:分治

考虑分治,假设当前统计的是[L,R]有多少个子区间合法,取mid=[(L+R)/2],统计包含mid和mid+1的合法区间个数
记p[i],q[i]分别表示i的出边指向点的最小值和最大值(不存在则为i)
则区间[l,r]合法的条件是
对于l<=i<=mid,p[i]>=l (1) 且 q[i]<=r (2)
对于mid<i<=r,p[i]>=l (3) 且 q[i]<=r (4)
条件(1)只和l有关,条件(4)只和r有关,做一遍前缀/后缀最值把合法的l,r处理出来即可
记Q[l]=max{q[i]|l<=i<=mid},条件(2)就是r>=Q[l]
如果记r'为最小的r>=mid满足p[r']<l,那么条件(3)就是r<r',且r'随l的减小而增大
那么只需从大到小枚举l,维护Q[l],单调维护r',答案加上[Q[l],r')内的合法r个数,这一部分复杂度是线性的
总复杂度O(nlogn),期望得分100分
如果复杂度不小心多写了一个log可能只有95分

 代码:

#include<cstdio>
#define maxn 300010
#define reg register
#define Fn "closure"
#define mod 1000000007
#define mid (lt+rt>>1)
typedef long long ll;
int n,m,ans;
int l[maxn],r[maxn],s[maxn];
inline int read(){
    reg int x=0,f=1;reg char c=getchar();
    for(;c<'0'||c>'9';f=c=='-'?-1:1,c=getchar());
    for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c=getchar());
    return x*f;
}
void bs(int lt,int rt){
    if(rt-lt==1){ans+=l[lt]==lt&&r[lt]==lt;return;}
    reg int mr=0;
    s[mid-1]=0;
    for(reg int i=mid;i<rt;i++){
        if(r[i]>mr)mr=r[i];
        s[i]=s[i-1]+(mr==i);
    }
    reg int ml=1<<30,pos=mr=mid;
    for(reg int i=mid;i-->lt;){
        if(l[i]<ml)ml=l[i];
        if(r[i]>mr)mr=r[i];
        while(pos<rt&&l[pos]>=i)pos++;
        if(ml==i&&pos>mr)(ans+=s[pos-1]-s[mr-1])%=mod;
    }
    bs(lt,mid);
    bs(mid,rt);
}
int main(){
    freopen(Fn".in","r",stdin);
    freopen(Fn".out","w",stdout);
    n=read();m=read();
    for(reg int i=1;i<=n;)l[i]=r[i]=i++;
    while(m--){
        reg int s=read(),t=read();
        if(t<l[s])l[s]=t;
        if(t>r[s])r[s]=t;
    }
    bs(1,n+1);
    printf("%d\n",ans);
    return 0;
}
View Code

T3:

题解:

部分分算法:搜索
暴搜t步移动,复杂度 O(4^t),期望得分32分


部分分算法:最短路+搜索

注意到,两个相邻的金币(或起点、终点)之间走的路径一定是最短路
所以BFS预处理出起点、终点和k个金币两两之间的最短路,然后搜索经过金币的顺序
复杂度 O(knm+k!),期望得分56分
如果偷懒把搜索部分写成 k!*k,可能只有52分


部分分算法:最短路+状压DP
同样先BFS处理最短路,用 d(i,j) 表示金币 i,j 的距离,起点为 0,终点为 k+1
记 f(i,S) 为当前位于第 i 个金币处(假设起点是第 0 个金币),之后经过金币集合 S 到终点的最短时间
当 S 为空时 f(i,S) = d(i,k+1)
否则 f(i,S) = min{f(j, S-{j}) + d(i,j) | j属于S}
复杂度 O(knm+2^k*k^2),期望得分64~68分


分析性质:
迷宫可认为是(n+1)(m+1)个点,(n+1)m+(m+1)n-nm=nm+n+m=(n+1)(m+1)-1边的无向连通图,即树
树上任意两点间存在唯一简单路径
考虑从起点s到终点t,拾取集合 S 内的金币的最优走法
记 P 为 s 到 t 路径,显然位于 P 上的边要走奇数次,其余边要走偶数次
将 P 上的点当作根,那么对于任意 S 中的点 v,v 到根的路径必须被经过,其余边都可以不经过
记 P 为s到 E' 为所有 S 中的点 v 到根的路径经过的边集
那么至少需要 |P|+2|E'| 的时间,显然按 P 的顺序逐个DFS子树可以达到这个下界


算法:树形DP

首先 P 上的金币都可以拾取,接下来令 t'=[(t-|P|+1)/2],然后把 P 缩成一个点 root
先预处理 s(i) 为以 i 为根的子树中的金币数量
记 f(i,j) 为以 i 为根的子树中取 j 个金币,经过边数的最小值
设 i 的子结点有 c[1],c[2],...,c[m]
g(i,j) 为以 c[1],c[2],...,c[i] 为根的子树中取 j 个金币,经过边数的最小值
那么 g(i,j)=max{g(i-1,j-j')+f(c[i],j')+[j'>0] | 0<=j'<=s(c[i]) 且 0<=j-j'<=s(c[1])+...+s(c[i-1])}
f(i,j) = g(m,j) + [i点有金币]
答案就是满足 f(root,ans)<=t' 的最大 ans
时间复杂度 O(knm),期望得分100分

代码:

#include<cstdio>
#define inf 0x3f3f3f3f
#define r register
#define Fn "maze"
#define maxn 505
#define maxe 251115
typedef long long ll;
int n,m,t,k,len;
int px[maxe],py[maxe];
bool w[maxn][maxn][4],c[maxe],vis[maxn][maxn];
const int dir[2][4]={{-1,0,0,1},{0,-1,1,0}};
#define C(a,b) (a*(m+1)+b)
inline int read(){
    r int x=0,f=1;r char c=getchar();
    for(;c<'0'||c>'9';f=c=='-'?-1:1,c=getchar());
    for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c=getchar());
    return x*f;
}
bool dfs(int x,int y){
    vis[x][y]=1;px[len]=x;py[len++]=y;
    if(x==n&&y==m)return 1;
    for(r int d=0;d<4;d++)
        if(!w[x][y][d]){
            r int xx=x+dir[0][d],yy=y+dir[1][d];
            if(xx>=0&&yy>=0&&xx<=n&&yy<=m&&!vis[xx][yy]&&dfs(xx,yy))return 1;
        }
    len--;return vis[x][y]=0;
}
int sz[maxe],f[maxe][101],g[101];
void dfs(int x,int y,int z){
    vis[x][y]=1;
    if(c[z])sz[z]++;
    for(r int d=0;d<4;d++)
        if(!w[x][y][d]){
            r int xx=x+dir[0][d],yy=y+dir[1][d];
            if(xx>=0&&yy>=0&&xx<=n&&yy<=m&&!vis[xx][yy]){
                r int v=C(xx,yy);dfs(xx,yy,v);
                for(r int i=0;i<=sz[z]+sz[v];i++)
                    g[i]=i<=sz[z]?f[z][i]:inf;
                for(r int i=0;i<=sz[z];i++)
                    for(r int j=1;j<=sz[v];j++)
                        if(f[z][i]+f[v][j]+1<g[i+j])g[i+j]=f[z][i]+f[v][j]+1;
                for(r int i=0;i<=sz[z]+sz[v];i++)
                    f[z][i]=g[i];
                sz[z]+=sz[v];
            }
        }
}
int main(){
    freopen(Fn".in","r",stdin);
    freopen(Fn".out","w",stdout);
    n=read();m=read();t=read();
    for(r int i=0;i<n*m;i++){
        r int x1=read(),y1=read(),x2=read(),y2=read(),d=0;
        for(;d<4&&(x2!=x1+dir[0][d]||y2!=y1+dir[1][d]);d++);
        w[x1][y1][d]=w[x2][y2][3-d]=1;
    }
    k=read();r int tot;
    for(r int i=0;i<k;i++)c[C(read(),read())]=1;
    dfs(tot=0,0);
    t=t-len+1>>1;
    for(r int i=0;i<len;i++){
        if(c[C(px[i],py[i])])tot++,k--;
        c[C(px[i],py[i])]=0;dfs(px[i],py[i],0);
    }
    for(;k&&f[0][k]>t;k--);
    printf("%d\n",k+tot);
    return 0;
}
View Code

AristocratMarser将于每周日或周一推送四校联考题解,请大佬们多多指教。

posted @ 2017-08-20 22:07  AristocratMarser  阅读(305)  评论(0编辑  收藏  举报