Solution -「OurOJ 46544」漏斗计算

\(\mathcal{Description}\)

  Link.

  定义一个运算结点 \(u\) 有两个属性:当前容量 \(x_u\)、最大容量 \(V_u\)。提供以下单元操作:

  1. I 读入一个整数 \(x\),令新结点 \(u=(x,x)\)。

  2. F u 装满 \(u\) 结点,即令 \(x_u=V_u\)。

  3. E u 清空 \(u\) 结点,即令 \(x_u=0\)。

  4. C s 令新结点 \(u=(0,s)\)。

  5. M u 令新结点 \(v=(0,x_u)\)。

  6. T u v 不断令 \(x_u\leftarrow x_u-1,x_v\leftarrow x_v-1\) 直到 \(x_u=0\) 或 \(x_v=V_v\)。

  构造不超过 \(10^4\) 次操作的一个运算方法,输入 \(a,b\),输出 \(ab\bmod 2^{18}\)。

  \(0\le a,b\le 10^5\)。

\(\mathcal{Solution}\)

  貌似是瓶子国的某个点?反正很离谱就是了。

  首先观察操作,我们只有一个二元运算 \(T(u,v)\),必然只能用它实现逻辑模块。

  再从问题入手,不难想到可以龟速乘计算 \(ab\),过程中若值 \(\ge 2^{18}\),则减去 \(2^{18}\)。那么我们至少需要实现这些模块:

  • 加法器:输入结点 \(u,v\),输出 \(w\),满足 \(x_w=x_u+x_v\)。

  • 逻辑减法器:输入结点 \(u,v\),输出 \(w\),若 \(x_u\ge x_v\),\(x_w=x_u-x_v\);否则 \(x_w=x_u\)。

  加法器比较方便:新建一个大容量点,把两个加数复制一份倒进去就好。先实现一个当前容量器:

inline int copyNum( const int u ) {
    printf( "M %d\n", u ), ++node;
    printf( "F %d\n", node );
    return node;
}

  再实现加法器:

inline int add( const int u, const int v ) {
    printf( "C 1000000000\n" ); int res = ++node;
    printf( "T %d %d\n", copyNum( u ), res );
    printf( "T %d %d\n", copyNum( v ), res );
    return res;
}

  逻辑减法?先要判断大小关系。而 \(T(u,v)\) 之后 \(x'_u=\max\{x_u-x_v,0\}\),我们只需要判断一个结点的当前容量是否为 \(0\)。好消息是,我们能够实现逻辑非器:

inline int logicNot( const int u ) {
    printf( "M %d\n", u ), ++node;
    puts( "C 1" ), ++node;
    printf( "F %d\n", node );
    printf( "T %d %d\n", node, node - 1 );
    return node;
}

  内部逻辑比较易懂就不讲啦。在此基础上,实现普通减法器和逻辑减法器:

inline int sub( const int u, const int v ) {
    printf( "M %d\n", v ); int tmp = ++node;
    printf( "T %d %d\n", copyNum( u ), tmp );
    return node;
}

inline PII logicSub( int u, int v ) {
    int f = logicNot( sub( u = add( u, 1 ), v ) );
    rep ( i, 1, 18 ) f = add( f, f );
    printf( "M %d\n", f ), f = ++node;
    printf( "T %d %d\n", v = copyNum( v ), f );
    return { u = sub( sub( u, v ), 1 ), f };
}

  逻辑减法器返回的 first 即减法结果,second 用于求解时重复利用。

  底层方法实现之后,剩下的工作就简单了:输入 \(a,b\),计算 \(2a,4a,\cdots,2^{16}a\),枚举 \(b\) 是否大于等于 \(2^{16}\dots2^0\),若是,则减去,答案加上对应的权。都能用以逻辑非为基础的模块实现。

  操作次数复杂度为 \(\mathcal O(\log^2 V)\)(倍增以及内部的逻辑减法),我实现的常数较大,不过封装成模块很易懂就是了。(

\(\mathcal{Code}\)

/*+Rainybunny+*/

#include <bits/stdc++.h>

#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )

typedef std::pair<int, int> PII;
#define fi first
#define se second

int node, mod;

namespace StandardModuleLibrary {

inline int logicNot( const int u ) {
    printf( "M %d\n", u ), ++node;
    puts( "C 1" ), ++node;
    printf( "F %d\n", node );
    printf( "T %d %d\n", node, node - 1 );
    return node;
}

inline int copyNum( const int u ) {
    printf( "M %d\n", u ), ++node;
    printf( "F %d\n", node );
    return node;
}

inline int add( const int u, const int v ) {
    printf( "C 1000000000\n" ); int res = ++node;
    printf( "T %d %d\n", copyNum( u ), res );
    printf( "T %d %d\n", copyNum( v ), res );
    return res;
}

inline int sub( const int u, const int v ) {
    printf( "M %d\n", v ); int tmp = ++node;
    printf( "T %d %d\n", copyNum( u ), tmp );
    return node;
}

inline PII logicSub( int u, int v ) {
    int f = logicNot( sub( u = add( u, 1 ), v ) );
    rep ( i, 1, 18 ) f = add( f, f );
    printf( "M %d\n", f ), f = ++node;
    printf( "T %d %d\n", v = copyNum( v ), f );
    return { u = sub( sub( u, v ), 1 ), f };
}

} using namespace StandardModuleLibrary;

int main() {
    freopen( "liver.out", "w", stdout );

    int x, y;
    printf( "C 1\nF 1\n" ), node = 1; // element 1.
    printf( "C 262144\nF 2\n" ), mod = node = 2; // module.
    puts( "I" ), x = ++node;
    puts( "I" ), y = ++node;

    int buc[30] = { x }, pwr[30] = {};
    int ans = ++node; puts( "C 1000000000" );

    pwr[0] = ++node, printf( "C 1\nF %d\n", node );
    rep ( i, 1, 16 ) {
        pwr[i] = ++node, printf( "C %d\nF %d\n", 1 << i, node );
        PII shit( logicSub( add( buc[i - 1], buc[i - 1] ), mod ) );
        buc[i] = shit.fi;
    }

    per ( i, 16, 0 ) {
        PII r( logicSub( y, pwr[i] ) );
        int x = r.se;
        rep ( j, 1, 18 - i ) x = add( x, x );
        printf( "M %d\n", x ), x = ++node;
        printf( "T %d %d\n", buc[i], x ), y = r.fi;
        ans = logicSub( add( ans, buc[i] ), mod ).fi;
    }

    printf( "P %d\n", ans );
    return 0;
}

上一篇:柳婼 の PAT甲级题解


下一篇:LeetCode 1143. 最长公共子序列(动态规划)