【9.2校内测试】【开学祭】【exgcd】【树规(背包】【模拟】
比较裸的$exgcd$的应用?
$exgcd$可以算出在$x$和$y$分别是最小正整数时的解。注意在这里因为有$a(x+\frac{b}{d})+b(y-\frac{a}{d})=c$,$d=gcd(a,b)$,所以$\frac{b}{d}$和$\frac{a}{d}$一定是整数,所以最小$x$的整数解%的应该是$\frac{b}{d}$而不是$b$,$y$同理。
算出两种情况的最小整数解后,$x$为最小正数时$x$一直在加,$y$一直在减,所以$x$最小时$y$都为非正数,则无解。判$y$时同理。这两种情况都排除后算出的$x$和$y$相当于是对应的最大$x$,$y$和最小$x$,$y$。算答案就用$x$大的减小的再除以$\frac{b}{d}$算得的就是其中一共有多少解。
最后就是要先判掉各种0的情况,避免$exgcd$中整除0或者$mod$0。
#include<iostream> #include<cstdio> #define LL long long using namespace std; void exgcd ( LL a, LL b, LL &x, LL &y, LL &d ) { if ( b == 0 ) { d = a; x = 1; y = 0; return ; } LL x0, y0; exgcd ( b, a % b, x0, y0, d ); x = y0; y = x0 - ( a / b ) * y0; } int main ( ) { freopen ( "fuction.in", "r", stdin ); freopen ( "fuction.out", "w", stdout ); int T; scanf ( "%d", &T ); while ( T -- ) { LL a, b, c; scanf ( "%I64d%I64d%I64d", &a, &b, &c ); if ( !a && !b && c ) { puts ( "0" ); continue; } if ( !a && !b && !c ) { puts("ZenMeZheMeDuo"); continue; } if ( !a ) { if ( c % b != 0 || c / b < 0 ) printf ( "0\n" ); else puts("ZenMeZheMeDuo"); continue; } else if ( !b ) { if ( c % a != 0 || c / a < 0 ) printf ( "0\n" ); else puts("ZenMeZheMeDuo"); continue; } if ( a < 0 ) a = -a, b = -b, c = -c; LL x, y, d; exgcd ( a, b, x, y, d ); if ( c % d ) printf ( "0\n" ); else if ( b < 0 ) { printf ( "ZenMeZheMeDuo\n" ); continue; } else { x *= c / d; y *= c / d; LL numy; LL x1 = ( x % ( b / d ) + ( b / d ) ) % ( b / d ); if ( x1 == 0 ) x1 += b / d; LL y1 = ( c - x1 * a ) / b; LL y2 = ( y % ( a / d ) + ( a / d ) ) % ( a / d ); if ( y2 == 0 ) y2 += a / d; LL x2 = ( c - y2 * b ) / a; if ( y1 <= 0 || x2 <= 0 ) printf ( "0\n" ); else if ( ( x2 - x1 ) / ( b / d ) + 1 > 65535 ) printf ( "ZenMeZheMeDuo\n" ); else printf ( "%I64d\n", ( x2 - x1 ) / ( b / d ) + 1 ); } } return 0; }
一道以前就做过的经典树规+背包的题目。定义$dp[u][k]$表示$u$这棵子树内选择$k$个黑点能对答案产生的最大贡献。所以每次遍历子树时用背包的思想来计算当前连接到子树的边的贡献,更新子树的答案即可。
注意这里的初始化,最开始要把没有经过的状态定成-1,避免更新不存在的情况。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define RG register #define LL long long using namespace std; int n, m; void read ( int & x ) { x = 0; char ch = getchar ( ); while ( ch > '9' || ch < '0' ) ch = getchar ( ); while ( ch >= '0' && ch <= '9' ) { x = x * 10 + ch - '0'; ch = getchar ( ); } } struct Node { int v, nex; LL w; Node ( int v = 0, LL w = 0, int nex = 0 ) : v ( v ), w ( w ), nex ( nex ) { } } Edge[5005]; int h[2005], stot; void add ( int u, int v, LL s ) { Edge[++stot] = Node ( v, s, h[u] ); h[u] = stot; } int siz[2005]; LL dp[2005][2005]; void Dfs ( int u, int f ) { siz[u] = 1; memset ( dp[u], -1, sizeof ( dp[u] ) ); dp[u][0] = 0, dp[u][1] = 0; for ( RG int i = h[u]; i; i = Edge[i].nex ) { int v = Edge[i].v; if ( v == f ) continue; Dfs ( v, u ); siz[u] += siz[v]; for ( RG int j = min ( siz[u], m ); j >= 0; j -- ) for ( RG int k = 0; k <= min ( siz[v], j ); k ++ ) { LL tmp = 1LL * Edge[i].w * ( 1LL * k * ( m - k ) + 1LL * ( siz[v] - k ) * ( n - m - siz[v] + k ) ); tmp += dp[v][k]; if ( dp[u][j-k] != -1 ) dp[u][j] = max ( dp[u][j], dp[u][j-k] + tmp ); } } } int main ( ) { freopen ( "coloration.in", "r", stdin ); freopen ( "coloration.out", "w", stdout ); scanf ( "%d%d", &n, &m ); for ( int i = 1; i < n; i ++ ) { int u, v, s; read ( u ); read ( v ); read ( s ); add ( u, v, s ); add ( v, u, s ); } Dfs ( 1, 0 ); printf ( "%I64d", dp[1][m] ); return 0; }
把方向搞错了以至于现在不是很想写!!!
以下粘的题解:
光线只有遇上边界或堵塞的格子才会改变方向,所以改变方向的位置是有限的,光线的方向又最多只有四种,所以光线在循环之前改变方向的次数是$O(n+m+k)$级别的。我们可以模拟光线的移动。已知光线位置和光线的方向,使用二分的方法可以在$O(log k)$的时间复杂度内求出即将改变方向的位置和改变后的方向。
我们暂把光线的位置和方向称为光线的状态。一种状态能转移到一种且仅有一种状态。如果从状态$a$能转移到状态$b$,那么$b$反向后的状态能转移到$a$反向后的状态。所以一种状态能从一种且仅有一种状态转移而来。这就像是一种置换,所以从初始状态出发,必定会回到初始状态,并且回到初始状态之前不会重复经过某种状态。
我们对网格进行染色,有邻边的格子颜色不同,形成一个二分图。根据题目中光线反射的方式,可以发现,每当光线沿西北、东南方向前进时,只会经过一种颜色的网格,每当光线沿东北、西南方向前进时,只会经过另一种颜色的网格。所以光线在某一个格子中心时,要么只会是西北、东南方向之一,要么只会是东北、西南方向之一。
这样,如果一次循环内一个格子被重复经过,只有可能是光线以相反的两个方向进入,并且一次循环内一个格子最多被经过两次。一个格子被经过两次,所有被光线经过的格子都会被经过两次。易知,如果光线在前进过程中出现过如下两种反射,所有格子就会被经过两次。只需在模拟的过程中记录是否出现过这两种情况即可。
#include<iostream> #include<cstdio> #include<vector> #include<algorithm> using namespace std; const int N=100005; int n,m,k,dx,dy,x,y; long long ans; bool f; char ch[10]; struct qwe { int x,y; qwe(int X=0,int Y=0) { x=X,y=Y; } bool operator < (const qwe &b) const { return x<b.x||(x==b.x&&y<b.y); } }; vector<qwe>a[2]; int read() { int r=0,f=1; char p=getchar(); while(p>'9'||p<'0') { if(p=='-') f=-1; p=getchar(); } while(p>='0'&&p<='9') { r=r*10+p-48; p=getchar(); } return r*f; } void add(int x,int y) { a[0].push_back(qwe(x-y,x)); a[1].push_back(qwe(x+y,x)); } void wk() { int fl=(dx!=dy); qwe p=fl?qwe(x+y,x):qwe(x-y,x); vector<qwe>::iterator it=upper_bound(a[fl].begin(),a[fl].end(),p); for(;it->x!=p.x;it--); if(dx<0) for(;it->y>=x;it--); ans+=abs(x-it->y)-1; x=it->y,y=fl?it->x-x:x-it->x; bool u=binary_search(a[0].begin(),a[0].end(),qwe(x-y-dx,x-dx)),v=binary_search(a[0].begin(),a[0].end(),qwe(x-y+dy,x)); if(u==v) f=1,dx*=-1,dy*=-1; else if(u) x-=dx,dy*=-1; else if(v) y-=dy,dx*=-1; } int main() { freopen("ray.in","r",stdin); freopen("ray.out","w",stdout); n=read(),m=read(),k=read(); for(int i=1;i<=m;i++) add(0,i),add(n+1,i); for(int i=0;i<=n+1;i++) add(i,0),add(i,m+1); for(int i=1;i<=k;i++) { int x=read(),y=read(); add(x,y); } x=read(),y=read(); scanf("%s",ch); dx=(ch[0]=='N')?-1:1,dy=(ch[1]=='W')?-1:1; sort(a[0].begin(),a[0].end()); sort(a[1].begin(),a[1].end()); wk(); int sx=x,sy=y,sdx=dx,sdy=dy; ans=0; do wk(); while(!(x==sx&&y==sy&&dx==sdx&&dy==sdy)); printf("%I64d\n",(f)?(ans>>1):ans); return 0; }