NOIP2015解题报告

Day1.
当时的zxn很弱,弱到连dfs都调不明白就开始去NOIP。
现在他会了dfs,二分答案,求LCA,bfs,拓扑排序。
所以他回去填NOIP2015的题解坑。
T1.我现在依然不知道除了这种尾递归式的写法之外还有啥别的写法……
伪代码:

void dfs(int i,int j,int x)
{
    if(满足条件)w[i][j] = x;
    dfs(i',j',x + 1);
}

咳我好像现在明白了……
大概一个循环确实能搞出来……
T2.
求最小环,当时zxn心里确实也是这么想的。
由于他太弱了不会dfs,所以他并不知道怎么写。
题解:
dfs一遍,记一下时间戳。

void dfs(int x)
{
    vis[x] = 1;
    tid[x] = ++ tim;
    RepG(i,x)
        if(!vis[v])dfs(v);
        else ans = min(ans,tid[x] - tid[v] + 1);
    //大概是这样的吧
}

然后后来仔细一想,嗯我还是bfs吧。
次奥……?T掉了QAQ
嗯我们还是冷静一下,发现……
如果是bfs,需要拓扑排序,删掉没有用的那些点,只剩下环即可。

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define v edge[i].to
using namespace std;
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
int to[200005];
bool vis[200005],vis_now[200005];
int q[200005],tid[200005],tim = 0,ans = 1 << 30,end[200005];
int main()
{
    int n = read();
    Rep(i,n)
    {
        int a = read();
        to[i] = a;  
        end[to[i]] ++;
    }

    int h = 0,t = -1;   
    Rep(i,n)
        if(!end[i])vis[q[++ t] = i] = 1;
    while(h <= t){
        int x = q[h ++];
        end[to[x]] --;
        if(!end[to[x]])vis[q[++ t] = to[x]] = 1;
    }
    Rep(i,n){
        if(!vis[i]){
            h = 0,t = -1;
            vis[i] = 1;
            q[++ t] = i;
            tim = 0;
            tid[i] = 0;
            while(h <= t){
                int x = q[h ++];
                if(!vis_now[to[x]])vis_now[q[++ t] = to[x]] = 1,tid[to[x]] = ++ tim,vis[to[x]] = 1;
                else ans = min(ans,tim - tid[to[x]] + 1),h = t + 1;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

QAQ当时明明知道标算然而就是写不出来?
T3.
斗地主。
张地主出的一道水题。张地主亲口说道:“NOIP的一道水题。”
张地主这次CTSC还出了一道提交答案题。
“我们可以发现第8个点是个网格图。”
“第九个点是除了前几个调换了下顺序之外的网格图。”
“第十个点是挖掉了一些点的网格图。”
whx:“我要吐槽!怎么检验它是网格图呢?”
“我的暴力spfa怎么跑的这么慢呢……?”
TAT
题解:
暴力搜索即可。
我们考虑对于当前的手牌,先枚举顺子应该会优一点,因为这样方便加最优性剪枝。
我们考虑:
if(ans <= depth )return;
加这个剪枝就过了。

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define v edge[i].to
using namespace std;
int s[20];
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
int Cases,n,ans;
void dfs(int now){
    if(now >= ans)return;
    int a = 0,b = 0,c = 0;
    Rep_0(i,14)if(s[i] == 1)a ++;
    Rep_0(i,13)if(s[i] == 2)b ++;
    Rep_0(i,13)
        if(s[i] == 4){
            c ++;
            if(a >= 2)a -= 2;
            else if(b >= 2)b -= 2;
            else if(b >= 1)b --;
        }
    Rep_0(i,13)
        if(s[i] == 3){
            c ++;
            if(a >= 1)a --;
            else if(b >= 1)b --;
        }
    ans = min(ans,a + b + c + now);
    Rep_0(i,8){
        int j;
        for(j = i ; j <= 11 ; j ++){
            s[j] --;
            if(s[j] < 0)break;
            if(j - i >= 4)dfs(now + 1);
        }
        if(j == 12)j --;
        while(j >= i)s[j --] ++;
    }
    Rep_0(i,10){
        int j;
        for(j = i; j <= 11; j ++){
            s[j] -= 2;
            if(s[j] < 0)break;
            if(j - i >= 2)dfs(now + 1);
        }
        if(j == 12)j --;
        while(j >= i)s[j --] += 2;
    }
    Rep_0(i,11){
        int j;
        for(j = i; j <= 11; j ++){
            s[j] -= 3;
            if(s[j] < 0)break;
            if(j - i > 0)dfs(now + 1);
        }
        if(j == 12)j --;
        while(j >= i)s[j --] += 3;
    }
}
int main()
{
    Cases = read(),n = read();
    while(Cases --){
        CLR(s,0);
        ans = 10005;
        Rep(i,n){
            int c = read(),col = read();
            if(c < 3 && c)s[10 + c] ++;
            else if(c >= 3)s[c - 3] ++ ;
            else s[13] ++;
        }
        dfs(0);
        if(s[13] == 2)ans ++;
        printf("%d\n",ans);
    }
    return 0;
}

当时NOIP的时候,zxn表示自己:
“这个可能是某种神奇的搜索,估计写不出来。”
mdzz。
Day2.
zxn:”day1好像挺水的,虽然我不会做,但是算法还是都看出来的。day2应该很友善.”
T1.
跳石头。
zxn:”什么叫二分答案???”
题解:我们对最终那个” 最小的距离最大” 进行二分答案。
也就是说,我们考虑二分那个值,判断是否可行。
现在问题在于怎么O(n)判断可行。
考虑现在有两块石头i和j(i < j )它们连在一起,现在它们的距离小于二分的答案,我们现在想一下该搬哪块……
嗯……
显然是搬走j更优。
我们现在需要考虑的仅仅是这个答案是否可行,所以我们现在面临的条件就是是否能让它花费的石头最少。
考虑这样:
NOIP2015解题报告_第1张图片
i和j的距离小于二分的距离,并且j和j + 1的距离也小于二分距离。
我们肯定是要继续往后走的。
我们删去i的话以后影响的距离并不受这个i石头的控制,但是我们删去j了之后,不仅消除了前面的i - > j的不合法,而且还有可能让后面的变得合法。
所以删去石头j,即当前扫到的这个石头更优。

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define v edge[i].to
using namespace std;
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
int n,m,L;
int dis[50005];
bool check(int x){
    int p = 0 ,k = 0;
    Rep(i,n + 1){
        if(dis[i] - dis[k] < x)p ++;
        else k = i;
        if(p > m)return 0;
    }
    return 1;
}
int Bin_ans(int l,int r){
    if(l == r)return l; 
    int mid = l + r + 1 >> 1;
    if(check(mid))return Bin_ans(mid,r);
    return Bin_ans(l,mid - 1);
}
int main()
{
    L = read(),n = read(),m = read();
    Rep(i,n)
        dis[i] = read();
    dis[0] = 0;
    dis[n + 1] = L;
    printf("%d\n",Bin_ans(1,L));
    return 0;
}

T2.
zxn在一个月前不可置信地问fsf:”这怎么可能是一道特别简单的DP?”
现在他看到这道题:”哦我自己真是智障。”
设f[i][j][k]表示A串到i,B串到j,一共搞了k个串连起来的方案数。

(a[i]==b[j] and a[i1]!=b[j1])f[i][j][k]=pf[p][j1][k1]

(a[i]==b[j] and a[i1]==b[j1])f[i][j][k]=pf[p][j1][k1]+f[i1][j1][k]

前缀和 + 滚动数组优化。
这就没了啊QAQ
T3.
zxn原来一直读错题了。
他读成了使得所有的花费总和最小。
题解:使得最大的花费最小,显然二分答案。
问题是怎么check。
我们可以利用它的LCA。
我们考虑这两个点(u,v),如果dis(u,v) >ans,那是要担责任的。
我们把u - > v的路径都标一遍,表示它们用过一遍,并且我们把距离答案最大的差记为lim。
假设我们有p条边要担责任。
当有一条边a它的边权 >= lim,且有use[a] == p,那么我们就可以删掉它了。
总的复杂度我一开始以为这是个暴力,所以一直觉得自己不会做。
现在想想每次复杂度是可以优化成O(n)的啊QAQ
考虑我们每次只标一下那两个节点和它们的LCA。
我们直接对它们到LCA的路径都加一下就好了,但是如果每一个都单独加的话显然是不优的。
我们考虑其实这个玩意可以用dfs序优化一发,就是我们每次找叶子节点进行往上的递推。
这样就是O(n)的辣QAQ
总复杂度是O( nlog2n )
所以zxn还是太弱了QAQ

你可能感兴趣的:(NOIP2015解题报告)