星星之火

[雅礼NOIP2018集训 day3]

考试的时候刚了T1两个小时线段树写了三个子任务结果发现看错了题目,于是接下来一个半小时我自闭了

result=历史新低

这告诉我们,打暴力要端正态度,尤其是在发现自己之前出锅的情况下要保持心态的平和,不能和今天的比赛一样后面差不多直接弃疗


T1:

题意就是我们要做多次倒三角的区间加,最后统计全部的异或和。不幸的是当我看到空间限制512MB的时候就直接暴力上线段树了,凉心出题人

正解是很巧妙的二维前缀和做法

考虑我们暴力怎么做--对倒三角的每一行差分,最后统计一次,这样的复杂度是$O(nq)$的

这个时候可以发现每一次倒三角我们改变的差分序列是可以二维差分优化的。其实就是对三角的竖着的直角边和那条斜边在维护差分数组,最后再统计答案就好了

其他的做法不会啊,果然还是太弱

#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cstring>
#include<time.h>
using namespace std;
typedef long long ll;

const int N=3e3+15; 
ll n,q;
ll a[N][N],b[N][N],c[N][N],d[N][N],e[N][N];
inline ll read()
{
    char ch=getchar();
    ll s=0,f=1;
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*f;
}
int main()
{
    freopen("u.in","r",stdin);
    freopen("u.out","w",stdout);
    //double st=clock();
    n=read();q=read();
    while (q--)
    {
        ll r=read(),c=read(),l=read(),s=read();
        a[r][c]+=s;a[r+l][c]-=s;
        b[r-c+n-1][r]-=s;b[r-c+n-1][r+l]+=s;
    }
    for (int i=1;i<=n;i++)    
        for (int j=1;j<=n;j++)
        {    
            c[i][j]=c[i-1][j]+a[i][j];
            d[i][j]=d[i-1][j-1]+b[i-j+n][i];
            e[i][j]=e[i][j-1]+c[i][j]+d[i][j]; 
        }
    /*for (int i=1;i<=n;i++)    
    {
        for (int j=1;j<=n;j++) 
            printf("%d ",c[i][j]+d[i][j]);
        printf("\n");
    }*/
    //printf("\n");
    ll ans=0;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) ans^=e[i][j];
    /*for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++) printf("%d ",e[i][j]);
        printf("\n");
    }*/
    printf("%lld\n",ans);
    //double ed=clock();
    //printf("%lf\n",ed-st); 
}
View Code

T2:

数据范围好像就是给你状压DP的,状态分别是当前还剩下的球(一个0/1序列)和剩下球的个数(这里有个坑点,不能直接通过0/1序列记忆化,因为最高位可能是0,这样可能出现长度不同但是0/1序列相同的两个状态)

我们数组记忆化肯定是不行的,那么大的就只好开map了。鉴于上述的坑点,看代码注释了解如何避免吧

所谓最优策略,其实就是状态转移的时候取max就好

还有就是关于那个erase函数,删掉一个球,相当于在一个二进制数里面去掉一位。设去掉的二进制位为k,笔者的思路就是把0-k-1先取出来(预处理111...这样的数字&一下就好了),再把原来的数后面变成0(注意0的个数要比原来的位数少1,因为有一位被去掉了)

然后这题好像就A了,考场上像个傻叉一样的写搜索,关键是之前的1分暴力写完了没打return 0,输出两个答案的我彻底凉凉了,22分暴力都没有拿到

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
#include<map>
using namespace std;

const int N=31;
int n,k;
int pre[N];
namespace calc
{    
    const int M=24;
    double a[1<<M+1];
    map <int,double> p[N];
    void init()
    {
        for (int i=0;i<1<<M+1;i++) a[i]=-1;
    }
    bool count(int bit,int len)
    {
        if (len<=M) return a[1<<len|bit]!=-1;//坑点处理在这儿,看到那个|没有?std太伟大了 
        else return p[len].count(bit);
    }
    double &find(int bit,int len)
    {
        if (len<=M) return a[1<<len|bit];
        else return p[len][bit];
    }
}
int erase(int bit,int i)
{
    return (bit&pre[i-1])|((bit>>(i+1))<<i);
}
double dfs(int bit,int len)
{
    if (len<=k) return 0;
    if (calc::count(bit,len)) return calc::find(bit,len);//记忆化 
    double &res=calc::find(bit,len);
    res=0;
    for (int i=0,j=len-1;i<=j;++i,--j)
    {
        if (i<j) res+=max(dfs(erase(bit,i),len-1)+(bit>>i&1),dfs(erase(bit,j),len-1)+(bit>>j&1))*2;//*2是因为正的第i个,反的第len-i+1个也是这个位置 
        else res+=dfs(erase(bit,i),len-1)+(bit>>i&1);
    }
    return res/=len;//别忘了/len 
}
int main()
{
    //freopen("v.in","r",stdin);
    //freopen("v.ans","w",stdout);
    scanf("%d%d",&n,&k);
    char ch[N];
    scanf("%s",ch);
    calc::init();
    pre[0]=1;
    for (int i=1;i<N;i++) pre[i]=pre[i-1]|(1<<i);
    int bit=0;
    k=n-k;
    for (int i=0;i<n;i++)
    {
        bit|=(ch[i]=='W')<<i;
    }
    printf("%.10lf\n",dfs(bit,n));
    return 0;
} 
View Code

T3:

有两个需要明确的性质

1.不要变的边我们不变,因为变了我们还要至少花费一次代价把它变回来

2.一棵树里变了的边的条数就是在这些边覆盖的点集中奇数度数点的个数/2(度数是翻转的边带来的)。这个好像挺显然的,因为一条翻转的边只会给两个端点带来奇数的度数,中间的都是偶数的度数

考虑dp,dp[x][0/1]表示节点x与父亲的边是否翻转,最少的奇数点的个数和翻转的总长度(我们的dp数组存的这样的结构体)

发现目标是在最少的奇数点的基础上最小化翻转的总长度,也就是前者优先,于是我们用pair

考虑如何转移

设x与父亲的边的类型是type(if (d==2) type=2 else type=c^d)

如果type==1||type==2的话,dp[x][0]显然可以设成inf了,我们只能转移dp[x][1]。设tmp0表示仅仅考虑x的子树,当前点x不是路径端点的最小代价(注意x可能是被一条路径经过);tmp1表示仅仅考虑x的子树,当前点是路径端点的最小代价。这两个如何计算参见代码。dp[x][1]=max((tmp0.fi+1,tmp0.se+1),(tmp1.fi,tmp1.se+1)) 

还有就是type==0||type==2,这种情况也差不多,不理解参考代码吧

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#define pii pair<int,int>
using namespace std;

const int N=1e5+15;
const int inf=1e9+7;
int n;
vector <pii> mp[N];
pii dp[N][2];
inline int read()
{
    char ch=getchar();
    int s=0,f=1;
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*f;
}
pii operator + (pii a,pii b){return make_pair(a.first+b.first,a.second+b.second);}
void dfs(int x,int fa,int type)
{
    pii tmp0(0,0),tmp1(inf,inf);//tmp0是不以它为断电答案,tmp1是以它为端点的最小答案
    for (int i=0;i<mp[x].size();i++) 
    {
        int y=mp[x][i].first;
        if (y==fa) continue;
        dfs(y,x,mp[x][i].second);
        pii nxt0,nxt1;
        nxt0=min(tmp0+dp[y][0],tmp1+dp[y][1]);
        nxt1=min(tmp0+dp[y][1],tmp1+dp[y][0]);
        tmp0=nxt0;tmp1=nxt1;
    }
    if (type==2||type==0)
    {
        dp[x][0]=min(tmp0,make_pair(tmp1.first+1,tmp1.second));
    }
    else dp[x][0]=make_pair(inf,inf);
    if (type==2||type==1)
    {
        dp[x][1]=min(make_pair(tmp1.first,tmp1.second+1),make_pair(tmp0.first+1,tmp0.second+1));
    }
    else dp[x][1]=make_pair(inf,inf);
}
int main()
{
    n=read();
    for (int i=1;i<n;i++)
    {
        int a=read(),b=read(),c=read(),d=read();
        if (d!=2) d=c^d;
        mp[a].push_back(make_pair(b,d));
        mp[b].push_back(make_pair(a,d));
    }
    dfs(1,-1,0);
    printf("%d %d\n",dp[1][0].first/2,dp[1][0].second);
    return 0;
}
View Code
posted @ 2018-10-03 19:06  星星之火OIer  阅读(413)  评论(0编辑  收藏  举报