<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[fshp971]]></title><description><![CDATA[我好菜啊]]></description><link>https://fshp971.com/</link><image><url>https://fshp971.com/favicon.png</url><title>fshp971</title><link>https://fshp971.com/</link></image><generator>Ghost 2.9</generator><lastBuildDate>Mon, 09 Mar 2026 14:06:27 GMT</lastBuildDate><atom:link href="https://fshp971.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[2019华工校赛 - B, F, H, K题题解]]></title><description><![CDATA[<p>本场比赛四道中等题，其中三道题数据范围故意放水降低难度。<br>
然后。。没放水那题。。好像。。没人过啊。。？</p>
<h3 id="b">B - 修仙时在做什么？有没有空？可以来炼丹吗？</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/B">题目链接</a></p>
<p>M姓出题人：这么老的idea怎么还没人做XD</p>
<p>考虑到本题的idea非常古老，所以在预估难度时设为中等题，<s>然而现场没人过</s>。题意本质上就是给出一个含$2^{18}$个结点的无向连通图，再指定其中$n$个点，求这$n$个指定点的两两之间的最短路的最小值。</p>
<p>接下来的讨论假定$n$个指定点的编号两两不同（如果出现相同编号的话答案就是$0$）。我们不妨考虑二进制分组。枚举二进制位$0 \sim 17$，对每个枚举出的二进制位$2^k$，我们将这$n$个点按照编号对应的二进制位$2^k$为$0$还是$1$分成两组，</p>]]></description><link>https://fshp971.com/scut-2019-contest-medium/</link><guid isPermaLink="false">5cc565091ede3e0d46eb9e49</guid><category><![CDATA[ACM]]></category><dc:creator><![CDATA[fshp971]]></dc:creator><pubDate>Sun, 28 Apr 2019 08:34:54 GMT</pubDate><content:encoded><![CDATA[<p>本场比赛四道中等题，其中三道题数据范围故意放水降低难度。<br>
然后。。没放水那题。。好像。。没人过啊。。？</p>
<h3 id="b">B - 修仙时在做什么？有没有空？可以来炼丹吗？</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/B">题目链接</a></p>
<p>M姓出题人：这么老的idea怎么还没人做XD</p>
<p>考虑到本题的idea非常古老，所以在预估难度时设为中等题，<s>然而现场没人过</s>。题意本质上就是给出一个含$2^{18}$个结点的无向连通图，再指定其中$n$个点，求这$n$个指定点的两两之间的最短路的最小值。</p>
<p>接下来的讨论假定$n$个指定点的编号两两不同（如果出现相同编号的话答案就是$0$）。我们不妨考虑二进制分组。枚举二进制位$0 \sim 17$，对每个枚举出的二进制位$2^k$，我们将这$n$个点按照编号对应的二进制位$2^k$为$0$还是$1$分成两组，从一组往另一组跑多起点最短路，然后取个最小值就没了，总复杂度$O(18n \log n)$。</p>
<p>为什么这样做是正确的呢？考虑答案一定是某个编号为$u$的结点到某个编号为$v$的结点的最短路，而$u \neq v$，则他们一定在某个二进制位上是不相同的。当我们按照前述操作枚举到这个不相同的二进制位时，$u$和$v$一定会分到不同组中，因此跑完多起点最短路后一定会计算出全局最小值，于是这题就做完了。</p>
<pre><code>#include &lt;bits/stdc++.h&gt;

using namespace std;
typedef long long LL;
const int maxn = 2e5;
const int maxm = 18;
const int maxV = 1&lt;&lt;maxm;
const int INF = 0x3f3f3f3f;
const int P = 19260817;
int cost[maxV][maxm];
int start[maxn], mark[maxV], ans = INF, cc = INF, n;

inline LL PowMod(LL a, LL b) { LL r=1; while(b) { if(b&amp;1) r=r*a%P; a=a*a%P, b&gt;&gt;=1; } return r; }

void init()
{
    for (int s = 0; s &lt; maxV; ++s) {
        for(int i = 0; i &lt; maxm; ++i)
        {
            cost[s][i] = PowMod( max(s,s^(1&lt;&lt;i))%P, 1&lt;&lt;i ) % P + 1;
        }
    }
}
struct node
{
    int u,dis;
    node(int _u=0, int _dis = 0): u(_u), dis(_dis){}
    bool operator &lt;(const node&amp; nn) const
    {
        return dis &gt; nn.dis;
    }
};
priority_queue&lt;node&gt;que;
int dis[maxV];
void solve(int mask)
{
    while(!que.empty()) que.pop();
    for (int i = 0; i &lt; maxV; ++i) dis[i] = INF;
    for (int i = 0; i &lt; n; ++i)
        if(start[i] &gt;&gt; mask &amp; 1) 
        {
            dis[start[i]] = 0;
            que.push(node(start[i], 0));
        }
    while(!que.empty())
    {
        node tmp = que.top(); que.pop();
        int u = tmp.u, d = tmp.dis;
        if(d &gt;= ans) return;
        if(d != dis[u]) continue;
        if(d &amp;&amp; mark[u])
        {
            ans = d;
            return;
        }
        for(int i = 0; i &lt; maxm; ++i)
            if(dis[u^(1&lt;&lt;i)] &gt; d + cost[u][i])
            {
                dis[u^(1&lt;&lt;i)] = d + cost[u][i];
                que.push(node(u^(1&lt;&lt;i), dis[u^(1&lt;&lt;i)]));
            }
    }
}
int main()
{
    init();
    scanf(&quot;%d&quot;, &amp;n);
    for (int i = 0; i &lt; n; ++i) 
    {
        scanf(&quot;%d&quot;, &amp;start[i]);
        if(mark[start[i]]==1) exit( puts(&quot;0&quot;)*0 );
        mark[start[i]] = 1;
    }
    for (int i = 0; i &lt; maxm; ++i) solve(i);
    printf(&quot;%d\n&quot;,ans);
    return 0;
}
</code></pre>
<h3 id="f">F - 翻牌游戏</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/F">题目链接</a></p>
<p>这是一道<s>打表找规律</s>概率DP题。其实这题的数据范围可以出很大，但在某C姓出题人的极力阻止下改成了现在这个范围，也就是说允许直接交概率DP <s>（说到底还是因为CC handsome!）</s>。</p>
<p>设$f[i][k]$表示<strong>当前有$i$对对子完全没有翻开过、$k$个对子已经翻开过恰好一张时</strong>，在最佳策略下翻完所有牌所需操作数的期望值。那么我们对每个状态有以下两种操作：</p>
<ul>
<li>选两张都没有翻过的牌翻开；</li>
<li>选一张翻过的牌和一张没翻过的牌翻开。</li>
</ul>
<p>仔细讨论每种操作下的转移即可，时间复杂度$O(n^2)$，然后你就会发现$ANS = 2n-1$。此外你还会发现，第二个操作只有在恰好剩两张牌翻过两张牌没翻过时才会进行。</p>
<p>其实这题可以出更难，因为通过打表可以发现$f[n][m] = 2n-1+\frac{3}{2}m+\frac{m}{2(2n+m)}$。。。不过为了提升参赛体验，就不出这么自闭了。</p>
<pre><code>// CC!
#include&lt;bits/stdc++.h&gt;

using namespace std;

typedef double DB;

const int maxn = 5000;

DB f[maxn+5][maxn+5];
bool vi[maxn+5][maxn+5];

DB dfs(int n, int m) {
    if(vi[n][m]) return f[n][m];
    vi[n][m] = 1;
    if(n==0 &amp;&amp; m==0) return f[n][m] = 0;

    f[n][m] = 1e9;
    int hd = n*2+m;

    // nn策略
    if(hd&gt;=2) {
        DB tmp = 0;
        if(n&gt;=1) tmp += DB(n*2.0) / (hd*(hd-1.0)) * (dfs(n-1,m)+1); // 2张未知且相同
        if(n&gt;=2) tmp += (2.0*n*(2.0*n-2.0)) / DB(hd*(hd-1.0)) * (dfs(n-2,m+2)+1); // 2张未知但不同
        if(m&gt;=2) tmp += DB(m*(m-1.0)) / DB(hd*(hd-1.0)) * (dfs(n,m-2)+3); // 2张已知
        if(n&gt;=1 &amp;&amp; m&gt;=1) tmp += DB(4.0*n*m) / DB(hd*(hd-1.0)) * (dfs(n-1,m)+2); // 1张已知1张未知
        f[n][m] = min(f[n][m], tmp);
    }

    // nm策略
    if(m&gt;0) {
        DB tmp = 0;
        tmp += 1.0 / DB(hd) * (dfs(n,m-1)+1); // 2张已知且相同
        if(m&gt;=2) tmp += (m-1.0) / DB(hd) * (dfs(n,m-1)+2); // 2张已知但不同
        if(n&gt;=1) tmp += (2.0*n) / DB(hd) * (dfs(n-1,m+1)+1); // 1张未知1张已知
        f[n][m] = min(f[n][m], tmp);
    }

    return f[n][m];
}

int main() {
    int _; scanf(&quot;%d&quot;, &amp;_);
    while(_--) {
        int n; scanf(&quot;%d&quot;, &amp;n);
        printf(&quot;%.2f\n&quot;, dfs(n,0) );
    }
    return 0;
}
</code></pre>
<h3 id="hparco_love_gcd">H - Parco_Love_GCD</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/H">题目链接</a></p>
<p>这题原本的时间限制要更小，主要是为了卡掉RMQ的做法。但考虑到大家的参赛体验，就还是给多了时间让RMQ过了（现在来看或许不应该放水）。</p>
<p>两种做法。第一种做法是RMQ进行$O(n \log n \log 10^9)$的预处理搞出区间GCD值，然后每次就可以$O(\ log 10^9)$回答任意区间的GCD值。之后为了计算所有区间的GCD值之和，我们考虑$O(n)$枚举区间左端点，然后每次右端点从左往右二分+RMQ找到第一个会让区间GCD变化的位置，并累加答案。注意到右端点从左往右扫的过程中，GCD至多只会变化$O(\log 10^9)$次，所以这样做总复杂度就变成了$O(n \log n \log^2 10^9)$。</p>
<p>第二种做法是CDQ分治，也就是原本的正解。我们用函数$Solve(l,r)$来计算$[l,r]$的所有子区间的GCD之和。在每一层$Solve(l,r)$中，令$mid = \lfloor \frac{l+r}{2} \rfloor$，然后让左端点从$mid$往$l$扫，记录算出的GCD值及对应出现次数并扔到一个<code>map</code>里，同理让右端点从$mid+1$往$r$扫，将计算出的GCD值出现次数信息扔到另一个<code>map</code>里。现在我们有了两个<code>map</code>，他们的大小均不超过$O(\log 10^9)$，于是就可以$O(\log^2 10^9)$枚举两个<code>map</code>里的东西，同时$O(\log 10^9)$算一下GCD统计答案。</p>
<p>算一下第二种做法的复杂度。端点左右扫加上GCD以及<code>map</code>的复杂度后，所需的总复杂度为$O(n \log n \log 10^9)$。枚举<code>map</code>里的东西时，因为$Solve(1,n)$所涉及到的所有区间个数是$O(n)$级别的，所以这部分总复杂度为$O(n \log^3 10^9)$。综上CDQ分治的复杂度为$O(n (\log n \log 10^9 + \log ^3 10^9))$。</p>
<p>这里只贴CDQ分治的代码：</p>
<pre><code>#include&lt;bits/stdc++.h&gt;

using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=a; i&lt;=b; ++i)
#define PER(i,a,b) for(int i=a; i&gt;=b; --i)
#define MP make_pair
#define PB push_back
#define fi first
#define se second
typedef long long LL;
typedef double DB;

const int maxn = 5e5;
const int maxV = 1e9;
const int P = 1e9+7;

LL Gcd(LL a, LL b) { return b ? Gcd(b,a%b) : a; }

map&lt;int,int&gt; s1, s2;
int n, ai[maxn+5];
LL ans = 0;

inline void Add(LL &amp;a, LL b) { a+=b; if(a&gt;=P) a-=P; }

void Solve(int l, int r) {
    if(l==r) { Add(ans, ai[l]); return; }
    int mid = (l+r)&gt;&gt;1;
    s1.clear();
    s2.clear();

    int now = 0;
    for(int i = mid; i &gt;= l; --i) {
        now = Gcd(now, ai[i]);
        ++s1[now];
    }

    now = 0;
    for(int i = mid+1; i &lt;= r; ++i) {
        now = Gcd(now, ai[i]);
        ++s2[now];
    }

    for(auto pp : s1) {
        for(auto qq : s2) {
            Add(ans, Gcd(pp.fi, qq.fi) * pp.se % P * qq.se % P);
        }
    }

    Solve(l, mid);
    Solve(mid+1, r);
}

int main() {
    scanf(&quot;%d&quot;, &amp;n);
    assert(1&lt;=n &amp;&amp; n&lt;=maxn);
    REP(i,1,n) scanf(&quot;%d&quot;, ai+i);
    Solve(1,n);
    printf(&quot;%lld\n&quot;, ans);
    return 0;
}
</code></pre>
<h3 id="kparco_love_string">K - Parco_Love_String</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/K">题目链接</a></p>
<p>本题时间限制及数据范围继续放水，出题人现在就是很后悔_(:3」∠)_</p>
<p>第一种做法是Hash后STL乱搞，没什么好说的，唯一要注意的是现场专门卡掉了<code>map</code>，需要<code>unordered_map</code>或者<code>pb_ds</code>才能通过，但牛客上是没有卡<code>map</code>的。</p>
<p>第二种做法是SAM（后缀自动机）乱搞。问题的核心就是给出$A, B$两个串，如何快速$B$的每个子串在$A$中的出现次数之和。我们考虑对$A$建SAM，通过经典的Fail树结点基数排序后就可以算出SAM中<strong>每个结点对应的那个（那些）串在整个$A$串中的出现次数（代码中记为<code>occ</code>）</strong>。此外，对于每个SAM上每个结点的$len$成员（在标程代码中是<code>val</code>），<strong>预处理出它在SAM中对应的那个长度为$len$的串的所有子串在整个$A$串的出现次数之和（代码中记为<code>sum</code>）</strong>。然后拿$B$串在SAM上跑一遍顺便计个数就可以算出答案了，具体计数方法参考代码。总时间复杂度$O(n^2)$，<s>比Hash不知道高到哪里去</s>。</p>
<p>现场还有人用KMP也过了，大家可以自行思考，这里只贴SAM代码。</p>
<pre><code>#include&lt;bits/stdc++.h&gt;

using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=a; i&lt;=b; ++i)
#define PER(i,a,b) for(int i=a; i&gt;=b; --i)
#define MP make_pair
#define PB push_back
#define fi first
#define se second
typedef long long LL;
typedef double DB;

const int maxn = 1000, c_size = 26;

struct State {
    int val;
    LL sum, occ;
    int par, go[c_size];
    void init() { val = 0, sum = 0, occ = 0, par = -1, mem(go,-1); }
}que[maxn*2+5];
int root, last;
int tot;

void Extend(int w) {
    int p = last, np = tot++;
    que[np].init();
    que[np].val = que[p].val+1;
    que[np].occ = 1;
    while(p!=-1 &amp;&amp; que[p].go[w]==-1)
        que[p].go[w] = np, p = que[p].par;
    if(p == -1) que[np].par = root;
    else {
        int q = que[p].go[w];
        if(que[p].val+1 == que[q].val) que[np].par = q;
        else {
            int nq = tot++;
            que[nq] = que[q];
            que[nq].val = que[p].val+1;
            que[nq].occ = 0;
            que[np].par = que[q].par = nq;
            while(p!=-1 &amp;&amp; que[p].go[w]==q)
                que[p].go[w] = nq, p = que[p].par;
        }
    }
    last = np;
}


char str[maxn+5];
LL f[maxn+5];
int cont[maxn+5], S[maxn*2+5];

int main() {
    scanf(&quot;%s&quot;, str+1);
    int n = strlen(str+1);
    for(int kk = 1; kk &lt; n; ++kk) {
        root = last = 0;
        tot = 1, que[0].init();
        for(int i = 1; i &lt;= kk; ++i) Extend(str[i]-'a');
        for(int i=0; i&lt;=kk; ++i) cont[i] = 0;
        for(int i=0; i&lt;tot; ++i) ++cont[que[i].val];
        for(int i=1; i&lt;=kk; ++i) cont[i] += cont[i-1];
        for(int i=tot-1; i&gt;=1; --i) S[--cont[que[i].val]] = i;
        for(int i = tot-1; i &gt;= 1; --i) {
            int x = S[i];
            que[que[x].par].occ += que[x].occ;
        }
        for(int i = 1; i &lt; tot; ++i) {
            int x = S[i];
            que[x].sum = que[que[x].par].sum;
            que[x].sum += LL(que[x].val-que[que[x].par].val) * que[x].occ;
        }
        int p = root;
        int now = 0;
        f[kk] = 0;
        for(int i = kk+1; i &lt;= n; ++i) {
            int w = str[i]-'a';
            if(que[p].go[w] != -1) {
                p = que[p].go[w];
                ++now;
            }
            else {
                while(p!=-1 &amp;&amp; que[p].go[w]==-1) p = que[p].par;
                if(p == -1) p = root, now = 0;
                else {
                    now = que[p].val+1;
                    p = que[p].go[w];
                }
            }
            if(now&gt;0) {
                f[kk] += que[que[p].par].sum;
                f[kk] += LL(now - que[que[p].par].val) * que[p].occ;
            }
        }
    }
    int _; scanf(&quot;%d&quot;, &amp;_);
    while(_--) {
        int kk; scanf(&quot;%d&quot;, &amp;kk);
        printf(&quot;%lld\n&quot;, f[kk]);
    }
    return 0;
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[2019华工校赛 - D, G, J题解]]></title><description><![CDATA[<h3 id="d">D - 神经网络</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/D">题目链接</a></p>
<p>这是一道经典题目改编，原题是dreamoon出的NTU-WF选拔赛D题（<a href="https://codeforces.com/gym/101234/problem/D">题目链接</a>）。</p>
<p>不难发现，修复时所产生的花费，可以分解为一系列<strong>有向简单路径上结点权值之和</strong>。因此由期望的线性性可知，要求总花费的期望值，我们只需分别计算$n^2$条有向简单路径对总花费产生贡献的期望值。</p>
<p>对于树上一条有向简单路径$u \rightarrow v$，设该路径长度为$d \ (0 \leq d \leq n-1)$（也就是说有$d$条边，$d+1$个结点），路径上所有结点权值为$S$，那么结论就是：<strong>这条简单路径对总花费产生贡献的期望值为$\frac{S}{d}$ 。</strong></p>
<p>为什么这个结论是正确的呢？首先要注意到，对于任意一个边修复顺序，交换除<strong>有向路径$u \rightarrow v$上的$</strong></p>]]></description><link>https://fshp971.com/scut-2019-contest-hard/</link><guid isPermaLink="false">5cbec26e1ede3e0d46eb9e33</guid><category><![CDATA[ACM]]></category><dc:creator><![CDATA[fshp971]]></dc:creator><pubDate>Tue, 23 Apr 2019 07:45:41 GMT</pubDate><content:encoded><![CDATA[<h3 id="d">D - 神经网络</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/D">题目链接</a></p>
<p>这是一道经典题目改编，原题是dreamoon出的NTU-WF选拔赛D题（<a href="https://codeforces.com/gym/101234/problem/D">题目链接</a>）。</p>
<p>不难发现，修复时所产生的花费，可以分解为一系列<strong>有向简单路径上结点权值之和</strong>。因此由期望的线性性可知，要求总花费的期望值，我们只需分别计算$n^2$条有向简单路径对总花费产生贡献的期望值。</p>
<p>对于树上一条有向简单路径$u \rightarrow v$，设该路径长度为$d \ (0 \leq d \leq n-1)$（也就是说有$d$条边，$d+1$个结点），路径上所有结点权值为$S$，那么结论就是：<strong>这条简单路径对总花费产生贡献的期望值为$\frac{S}{d}$ 。</strong></p>
<p>为什么这个结论是正确的呢？首先要注意到，对于任意一个边修复顺序，交换除<strong>有向路径$u \rightarrow v$上的$d$条边</strong>以外的任意边的修复顺序，不影响有向路径$u \rightarrow v$对总花费的贡献。进一步分析我们发现，对于$u \rightarrow v$上的$d$条边，<strong>只有当连接着结点$u$的那条边是这$d$条边中最后一个被修复的边时，有向路径$u \rightarrow v$才会对总花费产生大小恰为$S$的贡献</strong>。不难证明“连接$u$的那条边在路径上的$d$条边中最后被修复”这一事件发生的概率为$\frac{1}{d}$，因此有向路径$u \rightarrow v$对最终总花费产生贡献的期望值就是$\frac{S}{d}$。</p>
<p>于是问题转化成，求树上所有有向路径的$\frac{\text{结点权值和}}{\text{路径长度}}$的和，而这可以通过树分治NTT计算。简单来说就是：我们每次找一个重心，对于重心的每个儿子对应的子树处理出两个值$cnt_i$和$val_i$，分别表示该子树中深度为$i$的结点个数，以及所有“深度为$i$的结点到该子树跟结点路径上的所有结点的权值之和”的和（如果没听懂就去看代码，<s>反正我也应该听不懂</s>）。于是我们对每棵子树都搞出了两个多项式。在每次计算跨越重心的贡献时，我们只需要合理利用$cnt_i$和$val_i$对应的多项式进行一波奥妙重重的NTT计数就统计出相应答案了，这之后再把这两棵子树的$cnt_i, val_i$分别合并即可。</p>
<p>需要注意的是，在计算某个分治出来的连通块的答案时，必须将子树按照对应多项式的大小排序，然后从小到大依次计算，否则会TLE（专门构造了卡未排序做法的数据，不过dreamoon出的那题没有卡掉未排序做法）。在有排序的做法下，处理每个连通块的时间复杂度为$O(size \log n)$，因此整个做法的复杂度为$O(n \log n \log n)$。</p>
<pre><code>#include&lt;bits/stdc++.h&gt;

using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=a; i&lt;=b; ++i)
#define PER(i,a,b) for(int i=a; i&gt;=b; --i)
#define MP make_pair
#define PB push_back
#define fi first
#define se second
typedef long long LL;
typedef double DB;

const int maxn = 1e5;
const int maxa = 1e5;

const LL P = ((7 * 17) &lt;&lt; 23) + 1, gg = 3;
const int maxL = 1&lt;&lt;17;

inline LL PowMod(LL a, LL b) { LL r=1; while(b) { if(b&amp;1) r=r*a%P; a=a*a%P, b&gt;&gt;=1; } return r; }
LL A[maxL+5], B[maxL+5];
void NTT(LL *a, int len, int type) {
    int i, j, k, l;
    for(i=1, j=len&gt;&gt;1; i&lt;len-1; ++i) {
        if(i&lt;j) swap(a[i], a[j]);
        k = len&gt;&gt;1;
        while(j&gt;=k)
            j -= k, k &gt;&gt;= 1;
        j += k;
    }
    LL var, step, u, v;
    for(l=2; l&lt;=len; l&lt;&lt;=1) {
        step = PowMod(gg, (P-1)/l);
        for(k=0; k&lt;len; k+=l) {
            var = 1;
            for(i=k; i&lt;k+l/2; ++i) {
                u = a[i], v = var*a[i+l/2] % P;
                a[i] = (u+v) % P;
                a[i+l/2] = (u-v+P) % P;
                var = var*step % P;
            }
        }
    }
    if(type == -1) {
        for(i=1; i&lt;len/2; ++i) swap(a[i], a[len-i]);
        LL inv = PowMod(len, P-2);
        for(i=0; i&lt;len; ++i) a[i] = a[i]*inv % P;
    }
}

int n, ai[maxn+5];
LL inv[maxn+5];
LL ans = 0;

vector&lt;int&gt; G[maxn+5];
int que[maxn+5], fa[maxn+5], sz[maxn+5], msz[maxn+5];
bool ban[maxn+5];
int FindRoot(int x) {
    int s = 1, t = 1;
    que[1] = x, fa[x] = 0;
    while(s &lt;= t) {
        x = que[s++], sz[x] = 1, msz[x] = 0;
        for(auto v : G[x])
            if(!ban[v] &amp;&amp; v!=fa[x])
                que[++t] = v, fa[v] = x;
    }
    for(int i = t; i &gt;= 1; --i) {
        x = que[i], msz[x] = max(msz[x], t-sz[x]);
        if((msz[x]&lt;&lt;1) &lt;= t) return x;
        sz[fa[x]] += sz[x], msz[fa[x]] = max(msz[fa[x]], sz[x]);
    }
    assert(false);
    return 0;
}

int id[maxn+5];
vector&lt;int&gt; cnt[maxn+5], val[maxn+5];
void Mul(int a, int b) {
    int l1 = cnt[a].size();
    int l2 = val[b].size();
    int len = 1;
    while(len &lt; l1+l2-1) len &lt;&lt;= 1;
    assert(len&lt;=maxL);
    REP(i,0,l1-1) A[i] = cnt[a][i];
    REP(i,l1,len-1) A[i] = 0;
    REP(i,0,l2-1) B[i] = val[b][i];
    REP(i,l2,len-1) B[i] = 0;
    NTT(A,len,1), NTT(B,len,1);
    REP(i,0,len-1) A[i] = A[i]*B[i] % P;
    NTT(A,len,-1);
    REP(i,1,len-1) ans = (ans + A[i] * inv[i]) % P;
}

void dfs(int x, int p, int d, int sum, vector&lt;int&gt; &amp;c, vector&lt;int&gt; &amp;v) {
    sum = (sum + ai[x]) % P;
    for(auto y : G[x]) if(y!=p &amp;&amp; !ban[y]) {
        dfs(y, x, d+1, sum, c, v);
    }
    if(d+1 &gt; c.size()) {
        c.resize(d+1);
        v.resize(d+1);
    }
    ++c[d], v[d] = (v[d]+sum) % P;
}

void Solve(int x) {
    x = FindRoot(x);
    int tot = 1;
    cnt[0].resize(1), val[0].resize(1);
    cnt[0][0] = 1, val[0][0] = ai[x];
    id[0] = 0;
    for(auto y : G[x]) if(!ban[y]) {
        cnt[tot].clear(), val[tot].clear();
        dfs(y, x, 1, 0, cnt[tot], val[tot]);
        id[tot] = tot;
        ++tot;
    }
    sort(id, id+tot, [&amp;](const int &amp;a, const int &amp;b){ return cnt[a].size() &lt; cnt[b].size(); });
    for(int i = 0; i+1 &lt; tot; ++i) {
        Mul(id[i], id[i+1]);
        Mul(id[i+1], id[i]);
        for(int k = 0; k &lt; cnt[id[i+1]].size(); ++k)
            val[id[i+1]][k] = (val[id[i+1]][k] + LL(ai[x])*cnt[id[i+1]][k]) % P;
        for(int k = 0; k &lt; cnt[id[i]].size(); ++k) {
            cnt[id[i+1]][k] = (cnt[id[i+1]][k] + cnt[id[i]][k]) % P;
            val[id[i+1]][k] = (val[id[i+1]][k] + val[id[i]][k]) % P;
        }
        cnt[id[i]].clear(), val[id[i]].clear();
    }
    cnt[id[tot-1]].clear(), val[id[tot-1]].clear();
    ban[x] = 1;
    for(auto y : G[x]) if(!ban[y]) Solve(y);
}

int main() {
    inv[1] = 1;
    REP(i,2,maxn) inv[i] = (P - P/i) * inv[P%i] % P;
    scanf(&quot;%d&quot;, &amp;n);
    REP(i,1,n) scanf(&quot;%d&quot;, ai+i);
    for(int i = 1, u,v; i &lt; n; ++i) {
        scanf(&quot;%d%d&quot;, &amp;u, &amp;v);
        G[u].PB(v);
        G[v].PB(u);
    }
    Solve(1);
    ans = ans*2 % P;
    printf(&quot;%lld\n&quot;, ans);
    return 0;
}
</code></pre>
<h3 id="g">G - 铁树开花</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/G">题目链接</a></p>
<p>本题是防AK题，原因不在于思维度高，而在于码量大……（标程在压过代码量的情况下200+行，不过牛客同步赛上杭电在比赛刚结束不久就通过了这题，并且他们准AK，非常强悍）</p>
<p>这题是把两个idea强行拼在一起的产物 <s>（于是也间接导致数据极其难造）</s>。第一个idea来自<a href="https://codingcompetitions.withgoogle.com/kickstart/archive/2019">Google Kick Start 2019 Round A P3 Contention</a>，也就是计算$\max( \min_{1 \leq i \leq m}(cnt_i) )$的方法。不妨考虑倒着染色，假设现在还有$i$种花的顺序没有确定，并且他们一定都是在前$i$步开花，那么我们先让他们都在树上开花染色，并且对于每朵花记录一个$temp_k$值，表示<strong>树上只由这一种花染过色的结点的个数</strong>。之后，我们只要选择$temp_k$最大的那朵花，让它在第$i$步开花，并用$temp_k$更新一下$\min_{1 \leq i \leq m}(cnt_i)$即可。如此反复做$n$次后就可以算出$\max( \min_{1 \leq i \leq m}(cnt_i) )$的值，这是一个贪心，仔细思考一下就会发现是对的。</p>
<p>下一个问题是，如何在每一轮快速计算$temp_k$。注意到开花半径最大不超过$2$，这给了我们乱搞的余地。我们考虑对树定根，然后按照深度建线段树，即把所有深度相同的结点放在连续的一段区间当中。然后对每个结点，我们记录以下它的所有深度为$1$的子结点对应的区间，然后在记录一下它的所有深度为$2$的子结点对应的区间，于是经过一番仔细的分类讨论后，你会发现每次开花染色或撤销操作只需要涉及$O(\log n)$个区间。由于需要记录当某个结点只被一朵花染色时，这朵花具体是哪个，所以可能还需要对结点多维护一个<code>set</code>，于是单次染色或撤销操作就变成了$O(\log n \log m)$。因此你就可以倒着做，让每朵花都在树上染色，然后每一轮找出最大的$temp_k$及其对应的花，然后把这朵花撤掉即可，具体实现看代码。</p>
<p>但是这样还没完，因为题目要求字典序最小的操作顺序 <s>（主要是因为数据不好造才强行加了这个特技增加题目复杂度）</s>，不过有了上面的一系列操作后，求字典序最小其实很简单。我们正着开花，每次选择没开过的花里面满足$temp_k \geq \max( \min_{1 \leq i \leq m}(cnt_i) )$且编号最小的那朵花开花即可。时间复杂度自己算算吧。。。</p>
<pre><code>#include&lt;bits/stdc++.h&gt;

using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=a; i&lt;=b; ++i)
#define PER(i,a,b) for(int i=a; i&gt;=b; --i)
#define MP make_pair
#define PB push_back
#define fi first
#define se second
#define lson (x&lt;&lt;1)
#define rson (x&lt;&lt;1|1)
#define mid ((l+r)&gt;&gt;1)
typedef long long LL;
typedef double DB;
typedef pair&lt;int,int&gt; pii;

const int maxn = 1e5;
const int maxM = 2e4;
const int maxnode = maxn&lt;&lt;2;

int n, m;
int DFN = 0, dfn[maxn+5], fa[maxn+5];
vector&lt;int&gt; vec[maxn+5];
int L1[maxn+5], R1[maxn+5], L2[maxn+5], R2[maxn+5];
int pt[maxM+5], dd[maxM+5];
int cnt[maxM+5];
bool dead[maxM+5];
vector&lt;int&gt; G[maxn+5];
priority_queue&lt;pair&lt;int,int&gt;&gt; q;

int tr[maxnode+5], lz[maxnode+5];
set&lt;int&gt; cov[maxnode+5];

void dfs(int x, int p, int dis) {
    fa[x] = p;
    vec[dis].PB(x);
    for(auto y : G[x]) if(y!=p) {
        dfs(y,x, dis+1);
    }
}

void dfs2(int x, int p) {
    L1[x] = L2[x] = n+1;
    R1[x] = R2[x] = 0;
    for(auto y : G[x]) if(y!=p) {
        dfs2(y,x);
        L1[x] = min(L1[x], dfn[y]);
        R1[x] = max(R1[x], dfn[y]);
        L2[x] = min(L2[x], L1[y]);
        R2[x] = max(R2[x], R1[y]);
    }
}

void reBuild(int x, int l, int r) {
    tr[x] = lz[x] = 0;
    assert(cov[x].size()==0);
    if(l&lt;r) {
        reBuild(lson, l, mid);
        reBuild(rson, mid+1, r);
    }
}

void PushDown(int x) {
    lz[lson]+=lz[x], tr[lson]+=lz[x];
    lz[rson]+=lz[x], tr[rson]+=lz[x];
    lz[x] = 0;
}

void Upd(int x, int l, int r, int v) {
    if(tr[x]&gt;1) return;
    if(cov[x].size()) v = *cov[x].begin();
    if(l==r) {
        if(tr[x]==0) tr[x] = n+10;
        else if(tr[x]==1) {
            assert(1&lt;=v &amp;&amp; v&lt;=n);
            ++cnt[v];
            q.push( pair&lt;int,int&gt;(cnt[v],v) );
            tr[x] = n+10;
        }
    }
    else {
        PushDown(x);
        Upd(lson, l, mid, v);
        Upd(rson, mid+1, r, v);
        tr[x] = min(tr[lson], tr[rson]);
    }
}

void Add(int x, int l, int r, int ll, int rr, int v) {
    if(ll&lt;=l &amp;&amp; r&lt;=rr) {
        ++tr[x], ++lz[x];
        cov[x].insert(v);
    }
    else {
        PushDown(x);
        if(ll&lt;=mid) Add(lson, l, mid, ll, rr, v);
        if(mid&lt;rr) Add(rson, mid+1, r, ll, rr, v);
        tr[x] = min(tr[lson], tr[rson]);
    }
}

void Sub(int x, int l, int r, int ll, int rr, int v) {
    if(ll&lt;=l &amp;&amp; r&lt;=rr) {
        --tr[x], --lz[x];
        auto it = cov[x].find(v);
        assert(it != cov[x].end());
        cov[x].erase(it);
    }
    else {
        PushDown(x);
        if(ll&lt;=mid) Sub(lson,l, mid, ll, rr, v);
        if(mid&lt;rr) Sub(rson, mid+1, r, ll, rr, v);
        tr[x] = min(tr[lson], tr[rson]) + lz[x];
    }
}

void Mod(int l, int r, int v, int o) {
    if(l&gt;r) return;
    if(o==1) Add(1, 1, n, l, r, v);
    else Sub(1, 1, n, l, r, v);
}

void Draw(int id, int o) {
    assert(o==1 || o==-1);
    int x = pt[id], kk = dd[id];
    if(kk==0) Mod(dfn[x], dfn[x], id, o);
    else if(kk==1) {
        Mod(L1[x], R1[x], id, o);
        Mod(dfn[x], dfn[x], id, o);
        if(fa[x]!=0) Mod(dfn[fa[x]], dfn[fa[x]], id, o);
    }
    else {
        Mod(L1[x], R1[x], id, o);
        Mod(L2[x], R2[x], id, o);
        if(fa[x]!=0) {
            int y = fa[x];
            Mod(L1[y], R1[y], id, o);
            Mod(dfn[y], dfn[y], id, o);
            if(fa[y]!=0) Mod(dfn[fa[y]], dfn[fa[y]], id, o);
        }
        else Mod(dfn[x], dfn[x], id, o);
    }
}

priority_queue&lt;int&gt; q2;

int main() {
    scanf(&quot;%d%d&quot;, &amp;n, &amp;m);
    for(int i = 1, u,v; i &lt; n; ++i) {
        scanf(&quot;%d%d&quot;, &amp;u, &amp;v);
        G[u].PB(v); G[v].PB(u);
    }
    dfs(1,0,1);
    REP(i,1,n) {
        for(auto x : vec[i]) dfn[x] = ++DFN;
    }
    dfs2(1,0);
    REP(i,1,m) {
        scanf(&quot;%d%d&quot;, pt+i, dd+i);
        Draw(i,1);
    }
    REP(i,1,m) q.push( pair&lt;int,int&gt;(0,i) );
    Upd(1, 1, n, -1);
    int ans = n+10;
    REP(i,1,m) {
        while(dead[q.top().se] || cnt[q.top().se]!=q.top().fi) q.pop();
        ans = min(ans, q.top().fi);
        int x = q.top().se; q.pop();
        assert(1&lt;=x &amp;&amp; x&lt;=m);
        dead[x] = 1;
        Draw(x,-1);
        Upd(1, 1, n, -1);
    }

    reBuild(1,1,n);
    while(!q.empty()) q.pop();
    REP(i,1,m) cnt[i] = 0, dead[i] = 0, Draw(i,1), q.push( pair&lt;int,int&gt;(0,i) );
    Upd(1, 1, n, -1);
    int gao = 0;
    REP(i,1,m) {
        while(gao &lt; m) {
            while(dead[q.top().se] || cnt[q.top().se]!=q.top().fi) q.pop();
            if(q.top().fi &lt; ans) break;
            q2.push( -q.top().se );
            dead[q.top().se] = 1;
            q.pop(); ++gao;
        }
        int x = -q2.top();
        q2.pop();
        Draw(x,-1);
        Upd(1, 1, n, -1);
    }

    printf(&quot;%d\n&quot;, ans);
    int tmp_sum = 0;
    REP(i,1,m) printf(&quot;%d\n&quot;, cnt[i]), tmp_sum += cnt[i];

    return 0;
}
</code></pre>
<h3 id="j">J - 单身狗救星</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/J">题目链接</a></p>
<p>把题目中的公式化简后，问题就转化成：给出二维平面内$n$个点，再给$m$个询问点，对每个询问点输出$n$个点中和它形成斜率最大的那个点的编号。</p>
<p>据出题人说这个idea去年jls在牛客多校上出过，做法是搞两遍凸包。我们把<strong>所有$n+m$点</strong>按照先$x$坐标后$y$坐标的顺序排个序，然后按照新顺序扫一遍，同时维护一个动态凸包。当我们扫到点是前$n$个定点之一，就把这个点加进凸包里；如果我们扫到的点是后$m$个询问点之一，我们就在凸包上二分找一个使得斜率最大的定点，并更新这个询问点的答案。</p>
<p>做完之后，再将所有点<strong>关于原点翻转</strong>，之后再做一遍上述操作即可。总时间复杂度$O((n+m) \log n)$。</p>
<p>为什么这样做是对的呢？简单口胡一下，当扫到某个询问点时，所有在其左侧的定点（也就是$x$座标小于它的点）一定都被包含在下凸包或者就在下凸包上。现在我们找到了下凸包上的斜率最大点，假设凸包内有一点能使得斜率更大，那么画画图就会发现一定能在凸包上找到另一个点使得斜率比这个凸包内点还要大，这样就出现了矛盾。所以这样操作一定能找到每个询问点<strong>左侧</strong>使其斜率最大的点，而对于<strong>右侧</strong>的情况，沿原点翻转再算一遍就出来了。</p>
<pre><code>// hls tql
#include &lt;bits/stdc++.h&gt;
#define ll long long

using namespace std;

struct node {
    ll x, y;
    int ty, id;
    node() {}
    node(ll _x, ll _y, int _ty, int _id)
        : x(_x), y(_y), ty(_ty), id(_id) {}
    void init(int _ty, int _id) {
        scanf(&quot;%lld%lld&quot;, &amp;x, &amp;y);
        ty = _ty;
        id = _id;
    }
    node operator-(const node &amp;b) const {
        return node(x - b.x, y - b.y, 0, 0);
    }
    ll operator^(const node &amp;b) const {
        return x * b.y - y * b.x;
    }
    bool operator&lt;(const node &amp;b) const {
        return x == b.x ? y &lt; b.y : x &lt; b.x;
    }
};

const int MAXN = 1e5 + 10;
node a[MAXN], b[MAXN], cc[MAXN &lt;&lt; 1];
int ans[MAXN], que[MAXN], lim;

bool judge(node a, node b, node c) {
    ll tmp = (b - a) ^ (a - c);
    return tmp == 0 ? c.id &lt; b.id : tmp &gt; 0;
}

void solve() {
    int las = 0;
    for (int i = 1; i &lt;= lim; i++) {
        if (cc[i].ty == 1) {
            while (las &gt; 1 &amp;&amp; ((cc[i] - cc[que[las]]) ^ (cc[que[las]] - cc[que[las - 1]])) &gt; 0)
                las--;
            que[++las] = i;
        } else {
            if (!las)
                continue;
            int l = 1, r = las;
            while (l &lt; r) {
                int mid = (l + r) &gt;&gt; 1;
                if (((cc[i] - cc[que[mid + 1]]) ^ (cc[que[mid + 1]] - cc[que[mid]])) &gt; 0)
                    r = mid;
                else
                    l = mid + 1;
            }
            if (ans[cc[i].id] == -1 || judge(b[cc[i].id], a[ans[cc[i].id]], a[cc[que[l]].id]))
                ans[cc[i].id] = cc[que[l]].id;
        }
    }
}

int main() {
    int n, q;
    scanf(&quot;%d%d&quot;, &amp;n, &amp;q);
    lim = n + q;
    for (int i = 1; i &lt;= lim; i++) {
        if (i &lt;= n) {
            a[i].init(1, i);
            cc[i] = a[i];
        } else {
            b[i - n].init(2, i - n);
            cc[i] = b[i - n];
        }
    }
    memset(ans, -1, sizeof(ans));
    sort(cc + 1, cc + lim + 1);
    solve();
    reverse(cc + 1, cc + lim + 1);
    for (int i = 1; i &lt;= lim; i++) {
        cc[i].x = -cc[i].x;
        cc[i].y = -cc[i].y;
    }
    solve();
    for (int i = 1; i &lt;= q; i++)
        printf(&quot;%d\n&quot;, ans[i]);
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[2019华工校赛 - A, C, E, I, L题解]]></title><description><![CDATA[<p>本场比赛5道简单题，除了A题榜歪了以外，其他4题通过率均符合预期。</p>
<h3 id="anb">A - NB群友</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/A">题目链接</a></p>
<p>问题可以转化成求“积$\leq R$的数的个数” $-$ “积$\leq (L-1)$的数的个数”，于是我们可以dfs枚举$2 \sim 9$每个数字出现的次数，然后再组合数统计一下答案，剪剪枝就能过。</p>
<pre><code>#include&lt;bits/stdc++.h&gt;

using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=</code></pre>]]></description><link>https://fshp971.com/2019-scut-contest-easy/</link><guid isPermaLink="false">5cbdbb5c1ede3e0d46eb9ddc</guid><category><![CDATA[ACM]]></category><dc:creator><![CDATA[fshp971]]></dc:creator><pubDate>Mon, 22 Apr 2019 13:07:40 GMT</pubDate><content:encoded><![CDATA[<p>本场比赛5道简单题，除了A题榜歪了以外，其他4题通过率均符合预期。</p>
<h3 id="anb">A - NB群友</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/A">题目链接</a></p>
<p>问题可以转化成求“积$\leq R$的数的个数” $-$ “积$\leq (L-1)$的数的个数”，于是我们可以dfs枚举$2 \sim 9$每个数字出现的次数，然后再组合数统计一下答案，剪剪枝就能过。</p>
<pre><code>#include&lt;bits/stdc++.h&gt;

using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=a; i&lt;=b; ++i)
#define PER(i,a,b) for(int i=a; i&gt;=b; --i)
#define MP make_pair
#define PB push_back
#define fi first
#define se second
typedef long long LL;
typedef double DB;

const int maxT = 50;
const int P = 1e9+7;
const LL MaxInt = (1LL&lt;&lt;32) - 1;

LL Fac[100], InvFac[100];
LL C[100][100];
int cnt[20];

LL lim;
LL dfs(LL now, int pt) {
    if(pt&lt;=1) {
        int s = 0;
        LL ret = 1;
        REP(i,2,9) {
            s += cnt[i];
            ret = ret * C[s][cnt[i]] % P;
        }
        if(s==0) return 0LL;
        return ret;
    }
    LL ret = 0;
    cnt[pt] = 0;
    while(now &lt;= lim) {
        ret = (ret + dfs(now, pt-1)) % P;
        now *= pt, ++cnt[pt];
    }
    return ret;
}

int main() {
    C[0][0] = 1;
    for(int i = 1; i &lt;= 50; ++i) {
        C[i][0] = C[i][i] = 1;
        for(int k = 1; k &lt; i; ++k) C[i][k] = (C[i-1][k] + C[i-1][k-1]) % P;
    }
    int _; scanf(&quot;%d&quot;, &amp;_);
    while(_--) {
        LL a,b;
        scanf(&quot;%lld%lld&quot;, &amp;a, &amp;b);
        lim = b;
        LL sb = dfs(1,9);
        lim = a-1;
        LL sa = dfs(1,9);
        LL ans = (sb - sa + P) % P;
        printf(&quot;%lld\n&quot;, ans);
    }
    return 0;
}
</code></pre>
<h3 id="c">C - 六学家的困惑</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/C">题目链接</a></p>
<p>此题出题人对此题的定位是签到题，但组题人表示好像不怎么签到。。。</p>
<p>正解是贪心，然而现场很多人的贪心策略是直接比较两根管子四个端点处的数字大小，然后取最大的那个数，于是就WA了。考虑下面这个例子：</p>
<pre><code>595
575
</code></pre>
<p>最优的取球顺序应该是先取第一根管子的$5$，再取第一根管子的$9$。但如果使用上面的贪心策略，就有可能先取第二根管子的$5$，再取第二根管子的$7$，这样就错了。</p>
<p>正确做法应该是：在每次取球时，对于每根管子，分别从左往右读和从右往左读，这样就可以得到一共$4$个字符串。比较这$4$个字符串的字典序大小，取字典序最大的那个字符串的第一个字符对应的球即可。</p>
<p>单组数据复杂度$O(n^2)$。</p>
<pre><code>// 邵老师牛逼!
#include&lt;bits/stdc++.h&gt;

using namespace std;

const int maxT = 14;
const int maxn = 40;

int cmp(char *s1, int p1, int d1, int n1, char *s2, int p2, int d2, int n2) {
    int n = min(n1,n2);
    for(int i = 1; i &lt;= n; ++i) {
        if(s1[p1] &lt; s2[p2]) return -1;
        if(s1[p1] &gt; s2[p2]) return 1;
        p1+=d1, p2+=d2;
    }
    if(n1 &lt; n2) return -1;
    if(n1 &gt; n2) return 1;
    return 0;
}

int main() {
    int __; scanf(&quot;%d&quot;, &amp;__);
    assert(1&lt;=__ &amp;&amp; __&lt;=maxT);
    char s1[maxn+5], s2[maxn+5];
    char ans[maxn*2+5];
    for(int _ = 1; _ &lt;= __; ++_) {
        scanf(&quot;%s%s&quot;, s1, s2);
        int l1 = 0, r1 = strlen(s1)-1;
        int l2 = 0, r2 = strlen(s2)-1;
        int n = r1+r2+2;
        for(int i = 0; i &lt; n; ++i) {
            int di1 = 0, di2 = 0;
            if(cmp(s1, l1, 1, r1-l1+1, s1, r1, -1, r1-l1+1) &gt;= 0) di1 = 1;
            else di1 = -1;

            if(cmp(s2, l2, 1, r2-l2+1, s2, r2, -1, r2-l2+1) &gt;= 0) di2 = 1;
            else di2 = -1;

            int pt = 0, di = 0;
            if(cmp(s1, (di1==1)?l1:r1, di1, r1-l1+1, s2, (di2==1)?l2:r2, di2, r2-l2+1) &gt;= 0)
                pt = 1, di = di1;
            else pt = 2, di = di2;
            char c = 0;
            if(pt==1) {
                if(di==1) ans[i] = s1[l1++];
                else ans[i] = s1[r1--];
            }
            else if(pt==2) {
                if(di==1) ans[i] = s2[l2++];
                else ans[i] = s2[r2--];
            }
        }
        ans[n] = '\0';
        printf(&quot;Case #%d: %s\n&quot;, _, ans);
    }
    return 0;
}
</code></pre>
<h3 id="e">E - 数独挑战</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/E">题目链接</a></p>
<p><s>舞蹈链模板题。</s> 这题出在校赛就是给大家爆搜的，听说过舞蹈链的选手可能会被唬住，没听过的选手直接大力剪枝莽一莽就过了。至于有些人的爆搜TLE了？那只能说明你们的剪枝不够优秀。。。</p>
<p>以下只给出爆搜代码：</p>
<pre><code>#include&lt;cstdio&gt;
using namespace std;

int v[100][2],map[10][10];

bool judge(int x,int y,int k)
{
    for(int i=0;i&lt;9;i++)
        if(map[i][y]==k||map[x][i]==k)
            return false;
    int dx=x-x%3,dy=y-y%3;
    for(int i=0;i&lt;3;i++)
        for(int j=0;j&lt;3;j++)
            if(map[dx+i][dy+j]==k)
                return false;
    return true;
}

int dfs(int n)
{
    if(n&lt;0) return 1;
    for(int i=1;i&lt;=9;i++)
    {
        int x=v[n][0],y=v[n][1];
        if(judge(x,y,i))
        {
            map[x][y]=i;
            if(dfs(n-1)) return 1;
            map[x][y]=0;
        }
    }
    return 0;
}

int main()
{
    int num=0;
    for(int i=0;i&lt;9;i++)
        for(int j=0;j&lt;9;j++)
        {
            scanf(&quot;%d&quot;,&amp;map[i][j]);
            if(map[i][j]==0)
            {
                v[num][0]=i;
                v[num++][1]=j;
            }
        }
    dfs(num-1);
    for(int i=0;i&lt;8;i++)
    {
        for(int j=0;j&lt;8;j++)
            printf(&quot;%d &quot;,map[i][j]);
        printf(&quot;%d\n&quot;,map[i][8]);
    }
    for(int j=0;j&lt;8;j++)
        printf(&quot;%d &quot;,map[8][j]);
    printf(&quot;%d&quot;,map[8][8]);
    return 0;
}
</code></pre>
<h3 id="i">I - 炒股</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/I">题目连接</a></p>
<p><s>（据说某C姓出题人被这题卡了）</s></p>
<p>全场通过率第二高的题，贪心策略没什么好讲，看代码理解理解就懂了。唯一需要注意的是这题答案会爆<code>int</code>范围，因此需要用<code>long long</code>保存答案。</p>
<p>以下提供Python 3代码 <s>（于是完全不用考虑爆<code>int</code>的问题了）</s>：</p>
<pre><code>n = int( input() )

pa = int( input() )
ans = 0
for i in range(1,n):
    a = int( input() )
    if(a&gt;pa): ans += a-pa
    pa = a

print(ans)
</code></pre>
<h3 id="l">L - 石头剪刀布</h3>
<p><a href="https://ac.nowcoder.com/acm/contest/625/L">题目链接</a></p>
<p>由于觉得C题不够签到，为防止有队伍爆零而临时加的签到题 <s>（然而仍然没防住）</s>。</p>
<p>在这题上出现的错误包括但不限于：</p>
<ul>
<li>拼错单词</li>
<li><code>system(&quot;pause&quot;);</code></li>
<li><code>#include &quot;stdafx.h&quot;</code></li>
</ul>
<p>Emmm……我只能说，出题人尽力了。</p>
<pre><code>#include &lt;bits/stdc++.h&gt;
using namespace std;
int main()
{
    int n;
    string str;
    cin &gt;&gt; n;
    while (n-- &gt; 0) {
        cin &gt;&gt; str;
        if (str == &quot;Scissors&quot;) cout &lt;&lt; &quot;Rock\n&quot;;
        if (str == &quot;Rock&quot;) cout &lt;&lt; &quot;Paper\n&quot;;
        if (str == &quot;Paper&quot;) cout &lt;&lt; &quot;Scissors\n&quot;;
    }
    return 0;
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[2019年华南理工大学程序设计竞赛（春季赛）赛后总结]]></title><description><![CDATA[<p>咕咕咕姑姑～</p>
<p><a href="https://ac.nowcoder.com/acm/contest/625">比赛链接</a></p>
<h2 id="">题目难度设置</h2>
<style>
    table th:nth-of-type(1) { width: 500px; }
    table th:nth-of-type(2) { width: 500px; }
</style>
<table>
<thead>
<tr>
<th style="text-align:center">难度</th>
<th style="text-align:center">题号</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">签到</td>
<td style="text-align:center">L</td>
</tr>
<tr>
<td style="text-align:center">简单</td>
<td style="text-align:center">A, C, E, I</td>
</tr>
<tr>
<td style="text-align:center">中等</td>
<td style="text-align:center">F, H, K</td>
</tr>
<tr>
<td style="text-align:center">中等偏难</td>
<td style="text-align:center">B</td>
</tr>
<tr>
<td style="text-align:center">难</td>
<td style="text-align:center">D，J</td>
</tr>
<tr>
<td style="text-align:center">自闭</td>
<td style="text-align:center">G</td>
</tr>
</tbody>
</table>
<h2 id="">题解汇总</h2>
<p><a href="https://fshp971.com/2019-scut-contest/../2019-scut-contest-easy/">A，C，E，I，L题解</a></p>
<p><a href="https://fshp971.com/2019-scut-contest/../scut-2019-contest-medium/">B, F, H, K题解</a></p>
<p><a href="https://fshp971.com/2019-scut-contest/../scut-2019-contest-hard/">D, G, J题解</a></p>
<h2 id="">总结</h2>
<p></p>]]></description><link>https://fshp971.com/2019-scut-contest/</link><guid isPermaLink="false">5cb9cfcf1ede3e0d46eb9d84</guid><category><![CDATA[ACM]]></category><dc:creator><![CDATA[fshp971]]></dc:creator><pubDate>Fri, 19 Apr 2019 13:43:31 GMT</pubDate><content:encoded><![CDATA[<p>咕咕咕姑姑～</p>
<p><a href="https://ac.nowcoder.com/acm/contest/625">比赛链接</a></p>
<h2 id="">题目难度设置</h2>
<style>
    table th:nth-of-type(1) { width: 500px; }
    table th:nth-of-type(2) { width: 500px; }
</style>
<table>
<thead>
<tr>
<th style="text-align:center">难度</th>
<th style="text-align:center">题号</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">签到</td>
<td style="text-align:center">L</td>
</tr>
<tr>
<td style="text-align:center">简单</td>
<td style="text-align:center">A, C, E, I</td>
</tr>
<tr>
<td style="text-align:center">中等</td>
<td style="text-align:center">F, H, K</td>
</tr>
<tr>
<td style="text-align:center">中等偏难</td>
<td style="text-align:center">B</td>
</tr>
<tr>
<td style="text-align:center">难</td>
<td style="text-align:center">D，J</td>
</tr>
<tr>
<td style="text-align:center">自闭</td>
<td style="text-align:center">G</td>
</tr>
</tbody>
</table>
<h2 id="">题解汇总</h2>
<p><a href="https://fshp971.com/2019-scut-contest/../2019-scut-contest-easy/">A，C，E，I，L题解</a></p>
<p><a href="https://fshp971.com/2019-scut-contest/../scut-2019-contest-medium/">B, F, H, K题解</a></p>
<p><a href="https://fshp971.com/2019-scut-contest/../scut-2019-contest-hard/">D, G, J题解</a></p>
<h2 id="">总结</h2>
<p></p>]]></content:encoded></item><item><title><![CDATA[hihoCoder 1579 : 统计后缀数组相同的字符串个数]]></title><description><![CDATA[<p>今年北京网赛出了一道字符串计数论文题<a href="http://hihocoder.com/problemset/problem/1579">hihoCoder 1579</a>，简单来说就是给定一个后缀数组，统计有多少个仅含小写字母的字符串的后缀数组和它一样。</p>
<p>既然有论文，那就来学习一下咯。论文叫<a href="http://www.sciencedirect.com/science/article/pii/S0304397508000376"><em>Counting suffix arrays and strings</em></a>，里面有大段大段<s>看不懂</s>的数学证明，我这里就大概说下结论吧。</p>
<p>令$P[]$为一长度为$n$的后缀数组（此时$P[]$是$n$的一个排列）。<br>
令$R[]$表示$P[]$对应的rank数组。$R[i]=j$当且仅当$P[j]=i$。<br>
令$R[n+1]=0$，并定义数组$R_+[]$，$R_+[i] = R[P[</p>]]></description><link>https://fshp971.com/hihocoder-1579/</link><guid isPermaLink="false">5c22691031e72f05d2fae2c8</guid><category><![CDATA[ACM]]></category><category><![CDATA[字符串]]></category><category><![CDATA[组合数学]]></category><dc:creator><![CDATA[fshp971]]></dc:creator><pubDate>Tue, 10 Oct 2017 11:35:00 GMT</pubDate><media:content url="https://fshp971.com/content/images/2017/10/Screen-Shot-2017-10-10-at-10.09.54-PM.png" medium="image"/><content:encoded><![CDATA[<img src="https://fshp971.com/content/images/2017/10/Screen-Shot-2017-10-10-at-10.09.54-PM.png" alt="hihoCoder 1579 : 统计后缀数组相同的字符串个数"><p>今年北京网赛出了一道字符串计数论文题<a href="http://hihocoder.com/problemset/problem/1579">hihoCoder 1579</a>，简单来说就是给定一个后缀数组，统计有多少个仅含小写字母的字符串的后缀数组和它一样。</p>
<p>既然有论文，那就来学习一下咯。论文叫<a href="http://www.sciencedirect.com/science/article/pii/S0304397508000376"><em>Counting suffix arrays and strings</em></a>，里面有大段大段<s>看不懂</s>的数学证明，我这里就大概说下结论吧。</p>
<p>令$P[]$为一长度为$n$的后缀数组（此时$P[]$是$n$的一个排列）。<br>
令$R[]$表示$P[]$对应的rank数组。$R[i]=j$当且仅当$P[j]=i$。<br>
令$R[n+1]=0$，并定义数组$R_+[]$，$R_+[i] = R[P[i]+1] \ (1 \leq i \leq n)$。<br>
定义集合$U = \{i | i \in [1,n-1], R_+[i] &gt; R_+[i+1] \}$，并令$d = |U|$。<br>
最后再设字符集大小为$\sigma$。</p>
<p>那么我们有如下结论：</p>
<p><strong>定理1</strong>（原文定理4）：长度为$n$且对应的后缀数组为$P$的所有字符串的个数为$\binom{n+\sigma-d-1}{\sigma-d-1}$。<br>
注意公式中的$\sigma-d-1$需满足非负，若$d&gt;\sigma-1$，则满足条件的字符串不存在（个数为0）。</p>
<p><strong>定理2</strong>（原文定理5）：长度为$n$且对应的后缀数组为$P$的所有字符串中，满足恰由$k \ (k \leq \sigma)$种不同的字符组成的字符串的个数为$\binom{n-d-1}{k-d-1}$。<br>
和定理1类似，若$d &gt; k-1$，则满足条件的字符串不存在。</p>
<p>有了上述定理1的结论，我们就能圆满解决这道题目了。话说定理2也能拿来出题<s>报复社会</s>，而且出题思路感觉比定理1要灵活一些。</p>
<p>hihoCoder 1579的代码：</p>
<pre><code class="java">
import java.io.*;
import java.util.*;
import java.math.*;

public class Main {
    static final int maxn = (int)1e5;
    static BigInteger Calc(int l, int r) {
        BigInteger res = BigInteger.ONE;
        for(int i = l; i <= r;="" ++i)="" res="res.multiply(BigInteger.valueOf(i));" return="" res;="" }="" public="" static="" void="" main(string[]="" args)="" {="" scanner="" in="new" scanner(system.in);="" int="" t="in.nextInt()," n,="" u;="" int[]="" p="new" int[maxn="" +="" 5],="" r1="new" 5];="" for(;=""> 0; --T) {
            n = in.nextInt();
            for(int i = 1; i <= n;="" ++i)="" {="" p[i]="in.nextInt();" r1[p[i]]="i;" }="" int="" dd="0;" r1[n="" +="" 1]="0;" for(int="" i="1;" <="" if(r1[p[i]=""> R1[P[i + 1] + 1])
                    ++dd;
            if(dd > 25) { System.out.println("0"); continue; }
            int n1 = n + 26 - dd - 1, n2 = 26 - dd - 1;
            System.out.println(Calc(n1 - n2 + 1, n1).divide(Calc(1, n2)));
        }
    }
}
</=></=></code></pre>]]></content:encoded></item><item><title><![CDATA[Blog搭建流程（一）：在服务器上搭建Ghost]]></title><description><![CDATA[<p><strong>Upd 2018-12-26</strong>: 这篇流程仅针对ghost v0.x，新版ghost可以直接用官方推荐的工具ghost-cli来完成安装，我这几天也将博客升级成了ghost v2.x。</p>
<p>需要注意的是，ghost官方的安装教程是基于ubuntu的。如果你像我一样在centos7上安装ghost，数据库最好不要用<code>yum</code>中默认的mysql开源版本<strong>mariaDB</strong>，否则可能会出现各种神乎其神的bug。推荐使用官方源安装mysql，或者改用sqlite3。</p>
<hr>
<p>连续搞了几个晚上 <s>（肝要废了）</s>，今天终于弄好了Ghost Blog+域名+全局SSL <s>（好菜啊）</s>。那第一篇blog就来说说搭建流程咯。</p>
<h3 id="">准备服务器</h3>
<p>首先你需要买个服务器，国外的话可以考虑<a href="https://www.vultr.com">Vultr</a>或者<a href="https://www.digitalocean.com">DigitalOcean</a>，这两个有5刀／月的服务器卖；国内的不是很了解，但要注意若服务器在国内，建网站是需要去备案的。我用的是Vultr的5美元服务器（CPU*1 + 1G内存 + 1T流量 + 25G SSD），系统是CentOS 7。Ghost安装步骤基本参考<a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-ghost-on-centos-7">这篇文章</a>。</p>
<p>第一次进入系统后建议先修改ssh登陆端口。</p>]]></description><link>https://fshp971.com/build-blog-1/</link><guid isPermaLink="false">5c22691031e72f05d2fae2c6</guid><category><![CDATA[网络]]></category><dc:creator><![CDATA[fshp971]]></dc:creator><pubDate>Sat, 22 Jul 2017 07:24:57 GMT</pubDate><media:content url="https://fshp971.com/content/images/2018/12/b62dc954049244b68a9165f2e730ef3b_th.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://fshp971.com/content/images/2018/12/b62dc954049244b68a9165f2e730ef3b_th.jpeg" alt="Blog搭建流程（一）：在服务器上搭建Ghost"><p><strong>Upd 2018-12-26</strong>: 这篇流程仅针对ghost v0.x，新版ghost可以直接用官方推荐的工具ghost-cli来完成安装，我这几天也将博客升级成了ghost v2.x。</p>
<p>需要注意的是，ghost官方的安装教程是基于ubuntu的。如果你像我一样在centos7上安装ghost，数据库最好不要用<code>yum</code>中默认的mysql开源版本<strong>mariaDB</strong>，否则可能会出现各种神乎其神的bug。推荐使用官方源安装mysql，或者改用sqlite3。</p>
<hr>
<p>连续搞了几个晚上 <s>（肝要废了）</s>，今天终于弄好了Ghost Blog+域名+全局SSL <s>（好菜啊）</s>。那第一篇blog就来说说搭建流程咯。</p>
<h3 id="">准备服务器</h3>
<p>首先你需要买个服务器，国外的话可以考虑<a href="https://www.vultr.com">Vultr</a>或者<a href="https://www.digitalocean.com">DigitalOcean</a>，这两个有5刀／月的服务器卖；国内的不是很了解，但要注意若服务器在国内，建网站是需要去备案的。我用的是Vultr的5美元服务器（CPU*1 + 1G内存 + 1T流量 + 25G SSD），系统是CentOS 7。Ghost安装步骤基本参考<a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-ghost-on-centos-7">这篇文章</a>。</p>
<p>第一次进入系统后建议先修改ssh登陆端口。打开ssh的配置文件：</p>
<pre><code>vi /etc/ssh/sshd_config
</code></pre>
<p>找到<code># port 22</code>这一行，把前面的#号注释删掉，同时再添加一行：</p>
<pre><code>port xx
</code></pre>
<p>xx是你想要用的新的端口号。<br>
之后重启<code>sshd</code>服务：</p>
<pre><code>systemctl restart sshd.service
</code></pre>
<p>接下来在防火墙开放端口并使之生效：</p>
<pre><code>firewall-cmd --permanent --add-port=xx/tcp
firewall-cmd --reload
</code></pre>
<p>xx是你的端口号。然后退出后重新指定端口登陆：</p>
<pre><code>ssh -p xx root@12.34.56.78
</code></pre>
<p>如果设置正确那么此时应该就可以登陆上去了。如果无法登陆的话那就再从22端口登陆，检查设置是否正确。</p>
<p>最后我们要关闭22端口。重新打开<code>/etc/ssh/sshd_config</code>，把<code>port 22</code>那一行注释掉并重启<code>sshd</code>就可以了。</p>
<h3 id="ghost">安装Ghost</h3>
<p>首先更新软件包信息，然后安装unzip，npm和nginx：</p>
<pre><code>sudo yum update -y
sudo yum install unzip -y
sudo yum install nginx -y
sudo yum install npm -y
</code></pre>
<p>之后下载最新版Ghost：</p>
<pre><code>wget https://ghost.org/zip/ghost-latest.zip
</code></pre>
<p>下载好后将其解压到<code>/var/www/ghost/</code>目录下：</p>
<pre><code>sudo mkdir /var/www
sudo unzip -d /var/www/ghost ghost-latest.zip
</code></pre>
<p>切换到<code>/var/www/ghost/</code>目录并安装Ghost：</p>
<pre><code>cd /var/www/ghost/
sudo npm install --production
</code></pre>
<p>接下来开始配置Ghost，它的配置文件为<code>/var/www/ghost/config.js</code>，但这个文件一开始是不存在的，所以我们要将同一目录下的模版文件复制过去：</p>
<pre><code>sudo cp config.example.js config.js
</code></pre>
<p>打开<code>config.js</code>，找到<code>production</code>部分，将<code>url</code>修改成你服务器的IP地址或域名，例如：<code>url: 'xxx.xxx.xxx.xxx';</code>，同时记下<code>server</code>处的内网地址和端口，默认应该是<code>127.0.0.1:2368</code>。当你的Ghost运行时，内网用户将通过这个地址访问Ghost。</p>
<p>但是现在是无法从外网访问Ghost的，所以我们接下来要配置Nginx内网转发，使得Nginx把来自80端口的http请求转发到内网地址<code>127.0.0.1:2368</code>。</p>
<h3 id="nginx">配置Nginx实现内网转发</h3>
<p>刚刚的这些操作都是在<code>/var/www/ghost</code>下进行的，接下来我们将配置nginx内网转发。切换目录到<code>/etc/nginx</code>：</p>
<pre><code>cd /etc/nginx
</code></pre>
<p>打开Nginx的配置文件<code>nginx.conf</code>：</p>
<pre><code>vi nginx.conf
</code></pre>
<p>找到监听80端口（写着<code>listen 80</code>）的<code>server</code>段，将这个<code>server</code>段修改成：</p>
<pre><code>server {
	listen 80;
	server_name your_domain_or_ip_address;

	access_log /var/log/nginx/nginx.vhost.access.log;
	error_log /var/log/nginx/nginx.vhost.error.log;

	location / {
		proxy_pass http://127.0.0.1:2368;
		proxy_set_header Host $http_host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_buffering off;
	}
}
</code></pre>
<p>其中，<code>your_domain_or_ip_address</code>要改成你自己的域名或IP地址；<code>proxy_pass</code>填的就是Ghost的内网地址。</p>
<p>接下来重启Nginx：</p>
<pre><code>sudo service nginx restart
</code></pre>
<p>这时再切换到<code>/var/www/ghost</code>目录来启动Ghost：</p>
<pre><code>sudo npm start --production
</code></pre>
<p>现在在浏览器中输入你的IP地址并访问，就可看到你的博客了。</p>
<h3 id="ghostroot">让Ghost以非root权限在后台运行</h3>
<p>让Ghost以root权限运行是非常不安全的，因为Ghost一旦被人攻破，那么攻击者就很容易获得root权限。因此我们要单独开一个权限较低的用户来运行Ghost。</p>
<p>添加一个名字叫做<code>ghost</code>的用户，并让它成为Ghost所在目录的所有者：</p>
<pre><code>sudo adduser --shell /bin/bash ghost
sudo chown -R ghost:ghost /var/www/ghost/
</code></pre>
<p>接下来令Ghost成为一个系统服务。添加配置文件：</p>
<pre><code>sudo vi /etc/systemd/system/ghost.service
</code></pre>
<p>在这个配置文件里写入这些东西：</p>
<pre><code>[Unit]
Description=Ghost
After=network.target

[Service]
Type=simple

WorkingDirectory=/var/www/ghost
User=ghost
Group=ghost

ExecStart=/usr/bin/npm start --production
ExecStop=/usr/bin/npm stop --production
Restart=always
SyslogIdentifier=Ghost

[Install]
WantedBy=multi-user.target
</code></pre>
<p>保存配置文件并使其生效：</p>
<pre><code>sudo systemctl enable ghost.service
sudo sytemctl start ghost.service
</code></pre>
<p>现在访问<code>http://your_domain_or_ip_address</code>就可以看到你的博客了。若要停止Ghost只需输入命令：</p>
<pre><code>sudo systemctl stop ghost.service
</code></pre>
<p>重启Ghost只需输入：</p>
<pre><code>sudo systemctl restart ghost.service
</code></pre>
<h3 id="ghost">简单管理Ghost</h3>
<p>建立好Ghost的第一件事就是创建后台用户。访问：</p>
<pre><code>http://your_domain_or_ip_address/ghost/
</code></pre>
<p>初次登陆要创建新管理员，按照它的提示操作就可以了。注意如果忘记了密码的话，它是可以邮箱找回密码的，但这个功能需要自行配置，所以一个比较简单的方法是直接从后台操作数据库。这个网上有很多教程，这里就不说了。</p>
<p>Ghost的数据基本上都存在<code>/var/www/ghost/content</code>里面，所以备份博客时直接备份这个文件夹就可以了。</p>
<p>至此终于把Ghost肝完了，接下来还要肝域名和SSL <s>（主要是在肝SSL）</s>。过几天有空了再把第二篇补上。</p>
]]></content:encoded></item></channel></rss>