2018 Multi-University Training Contest 2

传送门

C - Cover

题意:
给出一张无向图,现在问最少多少次“一笔画”能够覆盖所有的边,并且输出方案数。

思路:

  • 在无向图中,存在欧拉路径当且仅当所有点度数都为偶数或者只有两个点的度数为奇数并且一个点为起点一个点为终点。
  • 因为一次“一笔画”最多消掉两个奇数点,所以最少的次数就为\(max(\frac{d}{2},1),d\)表示奇数点个数。
  • 关键在于怎么构造方案。
  • 最直接的想法就是将奇数点两两配对,然后每次求一遍欧拉回路,但不好控制终点。
  • 题解的做法就比较神奇,通过添加一些虚边来连接奇数点,那么现在所有点的度数就为偶数。之后从起点\(dfs\),若遇到虚边则答案++,否则就将边加入当且答案中。
  • 我也不太清楚为什么这样能够扣出所有的方案,但若通过虚边找到一个环,那么说明肯定找到一个方案,回溯时若一个点没有其它边,说明它肯定在这个环上;否则,还有其它路径,这个点可能会在其它路径上。
  • 所以这个的正确性或许就是,一条边被多个方案覆盖的话,选择哪种都行。

还没有写代码,就贴贴标程吧,两次dfs,第一次搜出连通块并且找到奇数点,第二次就执行类似于上述的算法。

Code

#include
using namespace std;
int n,m,w[100010],a[100010],b[300010],c[300010],d[300010],r=1,p;
vector f,x[100010];
bool u[100010];
inline void add_(int i,int j)
{
    w[i]++;
    b[++r]=j;
    c[r]=a[i];
    a[i]=r;
}
inline void add(int i,int j)
{
    add_(i,j);
    add_(j,i);
}
inline void dfs(int i)
{
    int j;
    u[i]=1;
    if(w[i]&1)
      f.push_back(i);
    for(j=a[i];j;j=c[j])
      if(!u[b[j]])
        dfs(b[j]);
}
inline void dfs2(int i)
{
    int j;
    for(j=a[i];j;j=c[j])
      if(!d[j])
        {
         d[j]=d[j^1]=1;
         dfs2(b[j]);
         if(j>2*m+1)
           p++;
         else
           x[p].push_back(j/2*(2*(j&1)-1));
        }
}
int main()
{
    int i,j,k;
    while(scanf("%d%d",&n,&m)!=EOF)
      {
       for(i=1;i<=m;i++)
         {
          scanf("%d%d",&j,&k);
          add(j,k);
         }
       for(i=1;i<=n;i++)
         if(!u[i] && w[i])
           {
            dfs(i);
            if(!f.size())
              {
               f.push_back(i);
               f.push_back(i);
              }
            for(j=2;j

D - Game

输出"YES"即可。

E - Hack It

题意:
构造\(01\)长宽都为\(n,n\leq 2000\)的矩阵,保证矩阵中不存在四个角都为\(1\)的矩形,并且\(1\)的个数不少于\(85000\)

思路:
构造十分神奇,我是想不出来的QAQ。
首先对矩阵分块,我们取一个素数\(p=47\),其实就相当于\(\sqrt{n}\)的样子,那么现在就有\(p*p\)个小矩形,每个矩形大小为\(p*p\)
若每行每列都只放一个,也就是每个小矩形就放\(p\)\(1\),那么最终的答案就有\(p^3\)\(1\),也差不多就是\(85000\)了。
然后构造的时候就循环放置,只保证存在一列有重复,其他列都各不重复,可以证明当\(p\)为素数时是存在方案的(这也就是为啥选素数的原因)。
比如对于\(p=5\)的情况,我们就类似于这样构造:

10000 10000 10000 10000 10000
10000 01000 00100 00010 00001
10000 00100 00001 01000 00010
10000 00010 01000 00001 00100
10000 00001 00010 00100 01000

01000 ...
01000 ...
...

可以证明,这样放置肯定不会存在重复,最终找到规律,只需要让\(a[kp+b][ip+(ki+b)\% p]=1\)即可。
证明挺好证的,构造四个点然后列等式即可,发现只有当\(p\)为质数时才不矛盾(但是一开始怎么想得到这是素数...)

这个题大概就这么做...我感觉想到素数分块这一步很难,可能是我积累少了吧...

upd:刚才发现这个可能和完全剩余系有关,设\(\{a_k\}\)为模\(p\)的完全剩余系,那么对于任意\(gcd(p,x)=1\)\(x\),都有\(\{xa_k\}\)为另一个完全剩余系,并且满足题中条件。
证明如下:
假设现在有\(x*a_i\equiv x*a_j\),那么即有\(x*(a_i-a_j)\equiv 0\),因为\(gcd(x,p)=1\),那么就有\(a_i\equiv a_j\)。也就是说当\(a_j\not ={a_i}\)时,有\(xa_j\not ={xa_i}\)
那么我们可以将\(p\)行为一块独立来看,除开所有都相同的那一列,其余的列都构成多个完全剩余系,也就是不会出现重复的。
多个块之间因为最多加\(p-1\),所以也不会出现重复。

感性证明就完成啦(为啥感觉写了一万年)

G - Naive Operations

题意:
给出两个序列\(a,b\),一开始\(a\)全为\(0\)\(b\)为一个排列。然后有两个操作:

  • \("add\ l\ r"\):\(a_l,a_{l+1},\cdots,a_r\)都加上1;
  • \("query\ l\ r"\):询问\(\sum_{i=l}^r\lfloor\frac{a_i}{b_i}\rfloor\)

思路:
线段树维护两个值即可,一个为答案,另一个为还剩多少就会\(+1\)的最小值。如果当前要\(+1\),线段树暴力更新即可。
可以知道\(+1\)不会超过\(\sum_{i=1}^n\lfloor\frac{n}{b_i}\rfloor=O(logn)\)次,所以复杂度大概\(O(nlog^2n)\)的样子。

Code

#include
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 1e5+5,MAXM = 4e5+5,MOD = 100003,INF = 0x3f3f3f3f;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-9;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define pii pair
#define vii vector
#define vi vector
using namespace std;
int n,q,b[MAXN],dif[MAXN<<2],mv[MAXN<<2];
ll sumv[MAXN<<2];
inline void pushUp(int o){
    dif[o]=min(dif[o<<1],dif[o<<1|1]);
    sumv[o]=sumv[o<<1] + sumv[o<<1|1];
}
void build(int o,int l,int r){
mv[o]=0;
    if(l==r){
        dif[o] = b[l];
        sumv[o]=mv[o]=0;
        return ;
    }
    int m=mid;
    build(lson);build(rson);
    pushUp(o);
}
inline void pushDown(int o){
    if(mv[o]){
        mv[o<<1] += mv[o];
        mv[o<<1|1] += mv[o];
        dif[o<<1] -=mv[o];
        dif[o<<1|1]-=mv[o];
        assert(dif[o<<1]>=0);
        assert(dif[o<<1|1]>=0);
        mv[o]=0;
    }
}
void update2(int o,int l,int r){
    if(l==r){
        dif[o] = b[l];mv[o]=0;
        sumv[o]++;
        return ;
    }
    int m=mid;
    pushDown(o);
    if(dif[o<<1]==0)update2(lson);
    if(dif[o<<1|1]==0)update2(rson);
    pushUp(o);
}
void update(int o,int l,int r,int L,int R){
    if(l>=L&&r<=R){
        dif[o]--;mv[o]++;
        if(dif[o]==0)update2(o,l,r);
        return ;
    }
    int m=mid;
    pushDown(o);
    if(L<=m)update(lson,L,R);
    if(R>m)update(rson,L,R);
    pushUp(o);
}
ll query(int o,int l,int r,int L,int R){
    if(dif[o]==0)update2(o,l,r);
    if(l>=L&&r<=R){
        return sumv[o];
    }
    int m=mid;
    ll ans=0;
    pushDown(o);
    if(L<=m)ans+=query(lson,L,R);
    if(R>m)ans+=query(rson,L,R);
    return ans;
}
int main(){
    ios::sync_with_stdio(false);
    while(cin>>n>>q){
        for(int i=1;i<=n;i++)cin>>b[i];
        build(1,1,n);
        while(q--){
            static char op[7];
            static int l,r;
            cin>>op>>l>>r;
            if(op[0]=='a')update(1,1,n,l,r);
            else cout<

J - Swaps and Inversions

求逆序对个数即可。

转载于:https://www.cnblogs.com/heyuhhh/p/11590635.html

你可能感兴趣的:(2018 Multi-University Training Contest 2)