NOI2017题解合集(施工中)

Day1

queue

Analysis

先考虑一种最暴力的算法:使用哈希表储存所有长度不超过 K=50 的子串,合并和分裂时我们只用修改交界处的 K2 个子串。时间复杂度 O(mK2)
考虑精细地实现程序:合并时我们只插入原本没有插入过的,也就是严格跨边界的。虽然单次复杂度可能达到 O(K2) ,但是均摊意义下是可以保证复杂度的:
设势函数 Φ 表示哈希表里面的串的个数,显然 Φ 有上界 O(nK) 。合并时每一次插入字符串我们相当于用 O(1) 的复杂度增加了 O(1) 的势能。删除的时候我们每次会减少 O(K2) 的势能。因此总的复杂度是 O(nK+cK2) 的。
为了减少算法常数,我们可以考虑用 Trie 树来代替哈希表,为了方便地迭代计算我们还要在每一个节点上处理出一个后继指针表示其删掉当前字符串的第一个字符它会跳到哪一个节点。注意求答案的时候跳到空节点我们就可以退出了。 Trie 的大小开 O(nK+cK2) 就好了。

Code

#include 
#include 
#include 
#include 
#include 

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int buf[30];

void write(int x)
{
    if (x<0) putchar('-'),x=-x;
    for (;x;x/=10) buf[++buf[0]]=x%10;
    if (!buf[0]) buf[++buf[0]]=0;
    for (;buf[0];putchar('0'+buf[buf[0]--]));
}

const int P=998244353;
const int L=10000005;
const int N=200005;
const int K=50;
const int LIM=N*K;
const int C_=1005;
const int EXT=C_*K*K;
const int S=LIM+EXT;
const int C=6;

int col[N],s[N],tmp[N],rtt[N],pre[N],suf[N];
int str[L];
int n,q,len,mid;

struct trie
{
    int ptr[S],cnt[S];
    int nxt[S][C];
    int tot,root;

    int newnode(){return ++tot;}

    void init(){root=newnode();}

    void modify(int sig)
    {
        int rt=root,rt_;
        for (int i=1;i<=mid;++i) rt=nxt[rt][s[i]];
        for (int i=1;ifor (int l=mid-i+1,j=mid+1;l0];++j)
            {
                ++l;
                if (!nxt[rt_][s[j]]) nxt[rt_][s[j]]=newnode();
                cnt[rt_=nxt[rt_][s[j]]]+=sig;
                if (sig>0)
                {
                    if (i>1) ptr[rtt[l+1]]=rt_;
                    rtt[l]=rt_;
                }
            }
        }
        rt_=root;
        for (int l=1,i=mid+1;l0];++i)
        {
            ++l,rt_=nxt[rt_][s[i]];
            if (!nxt[rt][s[i]]) nxt[rt][s[i]]=newnode();
            cnt[rt=nxt[rt][s[i]]]+=sig;
            if (sig>0)
            {
                if (mid>1) ptr[rtt[l+1]]=rt;
                rtt[l]=rt,ptr[rt]=rt_;
            }
        }
    }

    int query(int l)
    {
        int rt=root,ret=1;
        for (int i=0;i1;++i) rt=nxt[rt][str[i]];
        for (int i=l-1;ret&&i1ll*ret*cnt[rt=nxt[rt][str[i]]]%P;
        return ret;
    }
}t;

void process()
{
    t.init();
    for (int i=1;i<=n;++i)
    {
        if (!t.nxt[t.root][col[i]]) t.ptr[t.nxt[t.root][col[i]]=t.newnode()]=t.root;
        ++t.cnt[t.nxt[t.root][col[i]]];
    }
}

void link(int x,int y)
{
    suf[x]=y,pre[y]=x;
    tmp[0]=0;
    for (int i=1;x&&i0]]=col[x];
    reverse(tmp+1,tmp+1+tmp[0]),memcpy(s,tmp,(tmp[0]+1)*sizeof(int));
    mid=tmp[0],tmp[0]=0;
    for (int i=1;y&&i0]]=col[y];
    memcpy(s+s[0]+1,tmp+1,tmp[0]*sizeof(int)),s[0]+=tmp[0];
    t.modify(1);
}

void cut(int x)
{
    int y=suf[x],x_=x,y_=y;
    tmp[0]=0;
    for (int i=1;x&&i0]]=col[x];
    reverse(tmp+1,tmp+1+tmp[0]),memcpy(s,tmp,(tmp[0]+1)*sizeof(int));
    mid=tmp[0],tmp[0]=0;
    for (int i=1;y&&i0]]=col[y];
    memcpy(s+s[0]+1,tmp+1,tmp[0]*sizeof(int)),s[0]+=tmp[0];
    t.modify(-1);
    suf[x_]=pre[y_]=0;
}

int main()
{
    freopen("queue.in","r",stdin),freopen("queue.out","w",stdout);
    n=read(),q=read();
    for (int i=1;i<=n;++i) col[i]=read()-1;
    process();
    for (int opt;q--;)
    {
        opt=read();
        switch (opt)
        {
            case 1:
            {
                int x=read(),y=read();
                link(x,y);
                break;
            }
            case 2:
            {
                int x=read();
                cut(x);
                break;
            }
            default:
            {
                len=0;
                char ch=getchar();
                for (;!isdigit(ch);ch=getchar());
                for (;isdigit(ch);ch=getchar()) str[len++]=ch-'1';
                write(t.query(read())),putchar('\n');
            }
        }
    }
    fclose(stdin),fclose(stdout);
    return 0;
}

Day2

game

Analysis

首先考虑怎么把x给处理掉,可以发现我们 2d 枚举这些位置是否使用赛车A,就可以把问题变成没有x的情况。
然后我们把一个点两个能选择的赛车看成两种逻辑状态,这题就变成一个裸的 2SAT 问题了,注意不要忘了把反向边连上不然就没有对称性了。
时间复杂度 O(2d(n+m))

Code

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

char readc()
{
    char ch=getchar();
    while (!isalpha(ch)) ch=getchar();
    return ch;
}

const int N=50005;
const int V=N<<1;
const int M=100005;
const int E=M<<1;
const int E_=E<<1;
const int D=8;

int last[V],DFN[V],low[V],deg[V],rev[V],bel[V];
bool vis[V],chosen[V],exist[V];
int nxt[E_],tov[E_];
char ans[N],str[N];
int rule[M][4];
stack<int> st;
queue<int> q;
bool sig[E_];
int pos[D];
int tp[N];
int n,m,d,tot,idx;
bool found;

void insert(int x,int y,bool s=0){tov[++tot]=y,nxt[tot]=last[x],sig[tot]=s,last[x]=tot;}

void addedge(int x,int y){insert(x,y),insert(rev[y],rev[x]);}

int id(int x,int y){return !x?y-1:(x==2?y:y-(y>0));}
int id_(int x,int y){return !x?y+1:(x==2?y:y+(y>0));}

void tarjan(int x)
{
    DFN[x]=low[x]=++idx,st.push(x),exist[x]=1;
    for (int i=last[x],y;i;i=nxt[i])
        if (!DFN[y=tov[i]]) tarjan(y),low[x]=min(low[x],low[y]);
        else if (exist[y]) low[x]=min(low[x],DFN[y]);
    if (DFN[x]==low[x]) for (int y=0;x!=y;bel[y=st.top()]=x,st.pop(),exist[y]=0);
}

void floodfill(int x)
{
    if (vis[x]) return;
    vis[x]=chosen[x]=1;
    for (int i=last[x];i&&sig[i];i=nxt[i]) floodfill(tov[i]);
}

int main()
{
    freopen("game.in","r",stdin),freopen("game.out","w",stdout);
    n=read(),read();
    for (int i=1;i<=n;++i)
    {
        str[i]=readc();
        if (str[i]=='x') pos[d++]=i;
    }
    m=read();
    for (int i=1;i<=m;++i) rule[i][0]=read(),rule[i][1]=readc()-'A',rule[i][2]=read(),rule[i][3]=readc()-'A';
    for (int i=1;i<=n;++i) rev[i<<1]=i<<1|1,rev[i<<1|1]=i<<1;
    for (int s=0;!found&&s<1<memset(last,0,sizeof last),memset(nxt,0,sizeof nxt),tot=0;
        for (int i=1;i<=n;++i) tp[i]=str[i]-'a';
        for (int i=0;iif ((s>>i)&1) tp[pos[i]]=2,addedge(pos[i]<<1|1,pos[i]<<1);
            else tp[pos[i]]=0;
        for (int i=1;i<=m;++i)
            if (tp[rule[i][0]]!=rule[i][1])
                if (tp[rule[i][2]]==rule[i][3])
                {
                    bool s=id(tp[rule[i][0]],rule[i][1]);
                    addedge(rule[i][0]<<1|s,rule[i][0]<<1|s^1);
                }
                else addedge(rule[i][0]<<1|id(tp[rule[i][0]],rule[i][1]),rule[i][2]<<1|id(tp[rule[i][2]],rule[i][3]));
        memset(DFN,0,sizeof DFN),idx=0;
        for (int x=2;x<=(n<<1|1);++x) if (!DFN[x]) tarjan(x);
        bool judge=1;
        for (int i=1;judge&&i<=n;++i) if (bel[i<<1]==bel[i<<1|1]) judge=0;
        if (!judge) continue;
        found=1;
        for (int x=2;x<=(n<<1|1);++x)
            for (int i=last[x],y;i;i=nxt[i])
                if (bel[x]!=bel[y=tov[i]]) insert(bel[x],bel[y],1),++deg[bel[y]];
        for (int x=2;x<=(n<<1|1);++x) if (!deg[x]&&bel[x]==x) q.push(x);
        for (int x;!q.empty();)
        {
            x=q.front(),q.pop();
            if (vis[x]) continue;
            vis[x]=1,chosen[x]=0,floodfill(bel[rev[x]]);
            for (int i=last[x],y;i&&sig[i];i=nxt[i]) if (!--deg[y=tov[i]]) q.push(y);
        }
        for (int i=1;i<=n;++i) ans[i]='A'+id_(tp[i],chosen[bel[i<<1|1]]);
    }
    if (!found) printf("-1\n");
    else ans[n+1]='\0',printf("%s\n",ans+1);
    fclose(stdin),fclose(stdout);
    return 0;
}

vegetables

Analysis

我的方法比较复杂,这个思路应该还是可以继续优化一下的。
首先不要看错题了,这题的变质的意思是限定了第 i 种蔬菜中有 xi 个只能在第一天卖掉,有 xi 个只能在第二天及其以前卖掉……以此类推。
然后这样我们就可以构出一个很简单的费用流模型:每一天都建一个点,然后 S 向每一天都连一条容量为 m ,费用为 0 的边;每一天向 T 连若干条边,每一条边都是一种当天要过期的蔬菜,容量为当天过期的数量,费用为 ai 。由于我们可以卖掉后面才过期的蔬菜, i 天要向 i+1 天连一条容量为 + 费用为 0 的边。为了处理额外收益 s ,我们强制把一种蔬菜最后过期的某一棵费用变成 si+ai ,这样只要我们选了一种蔬菜,这一棵一定会被选,也就是说额外收益一定能够算上来。最后跑一遍最大费用最大流就可以求出某一个天数的答案。
现在我们考虑模拟这个费用流的过程。考虑到这副图的特殊性:有费用的边的终点都是 T ,因此退流不会影响到我的费用。于是模拟的就十分简单了,考虑从最后一个天向前,当前分配的 m 个流量只能用于这天以及以后的蔬菜,往前都不能使用,于是我们用一个堆来维护最后一天到现在的能卖掉的蔬菜,选出费用前 m 大的就好了。
这个算法朴素实现时能在 O(PnmlogPn) 的复杂度下模拟费用流过程,但是只能够求出某一天的答案。
我们考虑发掘每一天选出的蔬菜集合的特殊性质。容易发现,第 i 天选的蔬菜一定是第 i+1 天选的蔬菜的子集。考虑反证法:如果第 i 天选的蔬菜不是 i+1 天的子集,我们去掉并集之后设第 i 天还剩下集合 A ,第 i+1 天还剩下集合 B ,那么 A 中每一个元素一定都优于 B 中所有元素,因为第 i+1 天能选的第 i 天一定能选。然后考虑集合 A 在第 i+1 天不选的原因,无非是为了给 B 集合让位,但是 B 集合比 A 集合劣,因此这种情况是不成立的。
有了这个结论我们就可以很好地通过 x 天时的答案倒推出 x1 天时的答案。假设第 x 天实际使用了 vol 的流量,我们的目的就是要把这 vol 的流量删掉,同时要处理使用这些流量卖掉的蔬菜。考虑从价值最大的蔬菜开始退流,那么首先如果前面的天还有空余流量我们就把那个流量接过来,否则我们就比较一下使用前面流量卖掉的蔬菜中价值最小的是否小于当前要退流的蔬菜,如果小于就退掉前面那个换上当前的,否则就什么都不做。在这个过程中使用一些数据结构随便维护一下流量的对应关系就好了。时间复杂度大概是 O(PmlogPm) 的。
那么现在我们的瓶颈就在于如何求出最大那一天的费用流分配情况, O(PnmlogPn) 的算法显然是不够的。其实这个很好处理,我们只要不要每一条都暴力枚举所有种类的蔬菜,而采用一些预处理加上一个堆来维护一下就可以把时间复杂度降到 O(PmlogPm) 了。

Code

写的贼丑,STL不用钱系列
别人2k不到的贪心不知比我高到哪里去了。

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int buf[30];

void write(LL x)
{
    if (x<0) putchar('-'),x=-x;
    for (;x;x/=10) buf[++buf[0]]=x%10;
    if (!buf[0]) buf[++buf[0]]=0;
    for (;buf[0];putchar('0'+buf[buf[0]--]));
}

const int N=100005;
const int D=100000;

struct data
{
    int val,tot;

    bool operator<(data const d)const{return valbool operator>(data const d)const{return val>d.val||val==d.val&&tot>d.tot;}

    data (int val_=0,int tot_=0){val=val_,tot=tot_;}
};

int a[N],s[N],c[N],x[N],c_[N];
multiset<int> used[D+5],ext;
vector<int> veg[N],st[N];
multiset t;
set heap;
int vol[D+5];
LL ans[D+5];
data del[N];
int n,m,q,tot;

int rest(int id,int day){return c[id]-(day-1)*x[id]-c_[id];}

void calc()
{
    for (int i=1;i<=n;++i)
    {
        if (1ll*D*x[i]else veg[(c[i]-1)/x[i]+1].push_back(s[i]+a[i]);
        --c[i];
        if (1ll*D*x[i]else if (c[i]) st[(c[i]-1)/x[i]+1].push_back(i);
    }
    for (int i=D;i>=1;--i)
    {
        for (auto x:st[i]) heap.insert(data(a[x],x));
        for (auto x:veg[i]) ext.insert(-x);
        auto it=heap.rbegin();
        for (int j=1,x;(!ext.empty()||it!=heap.rend())&&j<=m;++j)
        {
            ++vol[i];
            if (it!=heap.rend()&&(ext.empty()||(*it).val>-(*ext.begin())))
            {
                data tmp=*it;
                ans[D]+=tmp.val,used[i].insert(tmp.val),t.insert(data(tmp.val,i)),++c_[tmp.tot];
                if (!rest(tmp.tot,i))
                {
                    if (!::x[tmp.tot]) del[++tot]=tmp;
                    ++it;
                }
            }
            else ans[D]+=x=-(*ext.begin()),used[i].insert(x),t.insert(data(x,i)),ext.erase(ext.begin());
        }
        for (;tot;--tot) heap.erase(del[tot]);
    }
    for (int i=D,cur=D;i>1;--i)
    {
        ans[i-1]=ans[i];
        for (multiset<int>::iterator it=used[i].begin();it!=used[i].end();++it) t.erase(t.find(data(*it,i)));
        for (int j=1;j<=vol[i];++j)
        {
            for (cur=min(cur,i-1);cur>0&&vol[cur]==m;--cur);
            int it=*used[i].rbegin();
            multiset<int>::iterator ptr=used[i].end();used[i].erase(--ptr);
            if (cur>0) used[cur].insert(it),++vol[cur],t.insert(data(it,cur));
            else if (t.begin()!=t.end())
            {
                data it_=*t.begin();
                if (it>it_.val) t.erase(t.begin()),used[it_.tot].erase(used[it_.tot].begin()),ans[i-1]-=it_.val,t.insert(data(it,it_.tot)),used[it_.tot].insert(it);
                else ans[i-1]-=it;
            }else ans[i-1]-=it;
        }
    }
}

int main()
{
    freopen("vegetables.in","r",stdin),freopen("vegetables.out","w",stdout);
    n=read(),m=read(),q=read();
    for (int i=1;i<=n;++i) a[i]=read(),s[i]=read(),c[i]=read(),x[i]=read();
    calc();
    for (;q--;) write(ans[read()]),putchar('\n');
    fclose(stdin),fclose(stdout);
    return 0;
}

你可能感兴趣的:(杂文,NOI)