ACM-ICPC 2018 南京赛区网络预赛(ABCDEFGHIJKL所有题题解大全)

新高一蒟蒻队的第一次ACM……
赛场上和队友时间安排不太恰当……只过了9题,第10题差半个小时2333……/还是自己太弱了
赛后经过一段时间把所有题全部A掉了2333……
A:题目链接
这道题在聊天中开场7分钟A掉了2333……真的是签到题……直接输出n-1即可。证明略。
B:题目链接
这道题似乎也是比较水的吧……单调栈+dp裸题。
我们先找到每个点向上走最近的黑点到它的距离记为 f ( i , j ) f(i,j) f(i,j),再考虑枚举合法矩形的右下角,并且找到满足 k &lt; j k&lt;j k<j f ( i , j ) &gt; = f ( i , k ) f(i,j)&gt;=f(i,k) f(i,j)>=f(i,k)的最大的k,这样对于当前合法的左端点<=k的矩形,必然和右下角再k的合法矩形一一对应,如果左端点>=k,答案就是 f ( i , j ) ∗ ( j − k ) f(i,j)*(j-k) f(i,j)(jk)。于是假设当前点答案为 g ( i , j ) g(i,j) g(i,j),则 g ( i , j ) = g ( i , k ) + f ( i , j ) ∗ ( j − k ) g(i,j)=g(i,k)+f(i,j)*(j-k) g(i,j)=g(i,k)+f(i,j)(jk)
f和g都可以直接dp出来,最大的k也可以单调栈找一下,然后就做完了(刚开始看AC人数少没敢做2333……)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 100005, maxm = 105;
int f[maxm], sta[maxm], T, n, m, K;
bool mt[maxn][maxm];
ll dp[maxm];
int main(){
	scanf("%d", &T);
	for(int cs = 1; cs <= T; cs++){
		memset(mt, 0, sizeof(mt));
		memset(f, 0, sizeof(f));
		memset(dp, 0, sizeof(dp));
		scanf("%d%d%d", &n, &m, &K);
		for(int i = 1; i <= K; i++){
			int x, y; scanf("%d%d", &x, &y);
			mt[x][y] = 1;
		}
		ll res = 0;
		for(int i = 1; i <= n; i++){
			int tp = 0;
			for(int j = 1; j <= m; j++){
				if(mt[i][j]) f[j] = 0;
				else ++f[j];
				while(tp > 0 && f[sta[tp]] >= f[j]) --tp;
				if(f[j]){
					dp[j] = dp[sta[tp]] + (ll)f[j] * (j - sta[tp]);
					res += dp[j];
				} else dp[j] = 0;
				sta[++tp] = j;
			}
		}
		printf("Case #%d: %lld\n", cs, res);
	}
	return 0;
}

C:题目链接
这道题大模拟不解释……(我居然用multiset写的,看来对stl依赖太多不是什么好事2333)

#include <bits/stdc++.h>
using namespace std;

const int maxn = 205;
multiset<int> ss[maxn];
int sta[20005], T, n, m;
const int rnk[15] = {0, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
const int id[15] = {0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2};
int main(){
	scanf("%d", &T);
	for(int cs = 1; cs <= T; cs++){
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= m; i++)
			scanf("%d", sta + (m - i + 1));
		int tp = m;
		for(int i = 1; i <= n; i++){
			ss[i].clear();
			int k = min(5, tp);
			for(int j = 0; j < k; j++)
				ss[i].insert(rnk[sta[tp--]]);
		}
		int p = 1, t = 0, num = -1;
		while(true){
			if(t == n - 1){
				for(int i = 0; i < n; i++){
					if(tp == 0) break;
					int k = (i + p - 1) % n + 1;
					ss[k].insert(rnk[sta[tp--]]);
				}
				num = -1;
			}
			int flag = 0;
			if(num == -1){
				num = *ss[p].begin();
				ss[p].erase(ss[p].begin());
			} else if(ss[p].find(num + 1) != ss[p].end()){
				ss[p].erase(ss[p].find(++num));
			} else if(ss[p].find(13) != ss[p].end())
				ss[p].erase(ss[p].find(num = 13));
			else flag = 1;
			if(flag) ++t;
			else t = 0;
			if(ss[p].empty()) break;
			if(num == 13){
				for(int i = 0; i < n; i++){
					if(tp == 0) break;
					int k = (i + p - 1) % n + 1;
					ss[k].insert(rnk[sta[tp--]]);
				}
				num = -1;
			} else p = p % n + 1;
		}
		printf("Case #%d:\n", cs);
		for(int i = 1; i <= n; i++){
			if(ss[i].empty()) puts("Winner");
			else{
				int sum = 0;
				for(multiset<int>::iterator it = ss[i].begin(); it != ss[i].end(); it++)
					sum += id[*it];
				printf("%d\n", sum);
			}
		}
	}
	return 0;
}

D:题目链接
平面几何神题,qzr julao上来就想出来的题Orzzzzzz……
首先整个圆在凸包里可以转化为把凸包所有边向内移动半径的长度,然后圆心在新凸包里即可。新凸包用半平面交算一下就行。考虑最大价值是多少,拆一下项就会发现这个东西实际上是三个圆心构成三角形面积的两倍,然后旋转卡壳求凸包内最大三角形即可。复杂度 O ( T n 2 ) O(Tn^2) O(Tn2)。(qzr巨佬说他的代码暂时不给看2333)
E:题目链接
这道题就是给你一些依赖关系,然后最大化某个值,裸的状压dp,初始值注意一下就行。(具体的思路其它博客很多)
不过cy julao似乎非常喜欢记忆化搜索??

#include<bits/stdc++.h>
#define pii pair< int,int> 
#define ll long long
#define pll pair<long long,long long>
using namespace std;
int buf[80];
template<class T> inline bool getd(T& x){
    int ch=getchar();
    bool neg=false;
    while(ch!=EOF && ch!='-' && !isdigit(ch)) ch=getchar();
    if(ch==EOF) return false;
    if(ch=='-'){
        neg=true;
        ch=getchar();
    }
    x=ch-'0';
    while(isdigit(ch=getchar())) x=x*10+ch-'0';
    if(neg) x=-x;
    return true;
}

template<class M> inline void putd(M x)
{
    int p=0;
    if(x<0){
        putchar('-');
        x=-x;
    }
    do{
        buf[p++]=x%(ll)10;
        x/=(ll)10;
    }while(x);
    for(int i=p-1;i>=0;i--) putchar(buf[i]+'0');
    putchar('\n');
}
int n;
ll inf=0x3f3f3f3f3f3f3f3f;
ll dp[1<<20];pii k[25];int need[25];int kl[25];int vis[1<<20];
int has[1<<20];ll ans=0;
int dfs(int mask)
{
	if(vis[mask]) return dp[mask];
	vis[mask]=1;
	for(int i=0;i<n;i++)
	{
		if(kl[i]&mask) continue;
		ll cc=k[i].first*has[mask]+k[i].second;
		if((need[i]&mask)==need[i]) dp[mask]=max(dp[mask],dfs(mask|kl[i])+cc);
	}
	//cout<<mask<<":"<<dp[mask]<<"  "; 
	ans=max(ans,dp[mask]);
	dp[mask]=max(dp[mask],0ll);
	return dp[mask];
}
int main()
{
    getd(n);for(int i=0;i<22;i++) kl[i]=(1<<i);
    for(int i=0;i<(1<<20);i++) has[i]=__builtin_popcount(i)+1; 
    for(int i=0;i<n;i++)
    {
    	getd(k[i].first);getd(k[i].second);
    	int tot;getd(tot);
    	while(tot--)
    	{
    		int id;getd(id);id--;need[i]|=kl[id];
		}
	}
	dfs(0);
	putd(ans);
}

F:题目链接
这道题在思维上有点难,而且实现上也比较毒瘤……
我们考虑如果在一棵树上从根节点出发回到根节点的期望边数是多少。不妨设 f ( x ) f(x) f(x)表示x节点回到root的期望边数,显然我们有f(root)=0.接下来考虑暴力的高斯消元(d(x)表示x的度数):
f ( u ) = ( ∑ ( u , v ) ∈ E f ( v ) / d ( u ) ) + 1 f(u)=\left(\sum_{(u,v)\in E}f(v)/d(u)\right)+1 f(u)=(u,v)Ef(v)/d(u)+1
直接高消不现实,我们考虑它有什么特殊性质。考虑叶节点u,则 f ( u ) = f ( p a r ) + 1 f(u)=f(par)+1 f(u)=f(par)+1。对于倒数第二层节点u,则 f ( u ) = f ( p a r ) + 2 d ( u ) − 1 f(u)=f(par)+2d(u)-1 f(u)=f(par)+2d(u)1,于是我们大胆假设,对于任意 f ( u ) f(u) f(u),存在 g ( u ) g(u) g(u)使 f ( u ) = f ( p a r ) + g ( u ) f(u)=f(par)+g(u) f(u)=f(par)+g(u)。那么 g ( u ) g(u) g(u)到底是什么呢?最后两层的规律告诉我们, g ( u ) = 2 s ( u ) − 1 g(u)=2s(u)-1 g(u)=2s(u)1,其中s(u)表示以u为根的子树大小。通过数学归纳法可以证明这个结论。
于是我们考虑答案是什么,由于第一步会任意走到根节点的一个子节点当中且不可能跳转到其它兄弟节点上,于是
a n s = ( ∑ ( r o o t , v ) ∈ E f ( v ) / d ( r o o t ) ) + 1 = 2 s ( r o o t ) − 2 d ( r o o t ) ans=\left(\sum_{(root,v)\in E}f(v)/d(root)\right)+1=\frac{2s(root)-2}{d(root)} ans=(root,v)Ef(v)/d(root)+1=d(root)2s(root)2
即总节点个数的两倍-2再除以根节点度数。于是我们需要维护连通块中节点个数和每个节点的度数。后者是很好维护的,前者可以考虑LCT维护子树信息,每个节点记录一个值表示该节点所有非偏爱子树的大小之和,于是连通块大小就是根所在链的所有点的权值之和。LCT做一下就行了,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 100005, mod = 998244353;
int fa[maxn], rev[maxn], son[maxn][2], val[maxn], sum[maxn];
int du[maxn], n, m;
int isroot(int x){
	return !fa[x] || (son[fa[x]][0] != x && son[fa[x]][1] != x);
}
void pushup(int x){
	sum[x] = val[x] + sum[son[x][0]] + sum[son[x][1]];
}
void pushdown(int x){
	if(!rev[x]) return;
	rev[son[x][0]] ^= 1;
	rev[son[x][1]] ^= 1;
	rev[x] = 0;
	swap(son[x][0], son[x][1]);
}
void rotate(int x){
	int y = fa[x], z = fa[y], w = son[y][0] == x;
	if(son[x][w]) fa[son[x][w]] = y;
	if(!isroot(y)) son[z][son[z][1] == y] = x;
	fa[y] = x, fa[x] = z;
	son[y][!w] = son[x][w], son[x][w] = y;
	pushup(y);
}
void splay(int x){
	stack<int> sta;
	sta.push(x);
	for(int i = x; !isroot(i); i = fa[i]){
		sta.push(fa[i]);
		assert(fa[i] != i);
	}
	while(!sta.empty()) pushdown(sta.top()), sta.pop();
	while(!isroot(x)){
		int y = fa[x], z = fa[y];
		if(!isroot(y)){
			if(son[z][0] == y ^ son[y][0] == x) rotate(x);
			else rotate(y);
		} rotate(x);
	}
	pushup(x);
}
void access(int x){
	for(int t = 0; x; t = x, x = fa[x]){
		splay(x);
		val[x] = val[x] - sum[t] + sum[son[x][1]];
		son[x][1] = t;
		pushup(x);
	}
}
void makeroot(int x){
	access(x), splay(x);
	rev[x] ^= 1;
}
int findroot(int x){
	access(x), splay(x);
	pushdown(x);
	while(son[x][0]){
		x = son[x][0];
		pushdown(x);
	}
	return x;
}
int link(int x, int y){
	makeroot(x);
	if(findroot(y) == x) return -1;
	val[y] += sum[x];
	pushup(y);
	fa[x] = y;
	++du[x], ++du[y];
	return 0;
}
void cut(int x){//between x and its father
	access(x), splay(x);
	pushdown(x);
	int f = son[x][0];
	pushdown(f);
	while(son[f][1]){
		f = son[f][1];
		pushdown(f);
	}
	--du[x], --du[f];
	son[x][0] = fa[son[x][0]] = 0;
	pushup(x);
}
int query(int x){//size of component
	access(x);
	splay(x);
	return sum[x];
}
ll modpow(ll a, int b){
	ll res = 1;
	for(; b; b >>= 1){
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
	}
	return res;
}
const int maxr = 10000000;
char str[maxr], prt[maxr];
int rpos, ppos, mmx;
char readc(){
	if(!rpos) mmx = fread(str, 1, maxr, stdin);
	if(rpos == mmx) return 0;
	char c = str[rpos++];
	if(rpos == maxr) rpos = 0;
	return c;
}
int read(){
	int x; char c;
	while((c = readc()) < '0' || c > '9');
	x = c - '0';
	while((c = readc()) >= '0' && c <= '9') x = x * 10 + c - '0';
	return x;
}
int print(int x){
	if(x){
		static char sta[10];
		int tp = 0;
		for(; x; x /= 10) sta[tp++] = x % 10 + '0';
		while(tp > 0) prt[ppos++] = sta[--tp];
	} else prt[ppos++] = '0';
	prt[ppos++] = '\n';
}
int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; i++) sum[i] = val[i] = 1;
	for(int i = 1; i < n; i++) link(read(), read());
	for(int cs = 1; cs <= m; cs++){
		int a = read(), b = read(), c;
		if(a == 1){
			if(link(b, read()) < 0) prt[ppos++] = '-', prt[ppos++] = '1', prt[ppos++] = '\n';
		} else if(a == 2){
			c = read();
			if(findroot(b) != findroot(c) || b == c){
				prt[ppos++] = '-', prt[ppos++] = '1', prt[ppos++] = '\n';
				continue;
			}
			makeroot(b);
			cut(c);
		} else print((2LL * query(b) - 2) * modpow(du[b], mod - 2) % mod);
	}
	fwrite(prt, 1, ppos, stdout);
	return 0;
}

G:题目链接
这道题考你的绝对是英语……那么长一个题面看了就要吐……
题目意思很简单,对于每个月我们不断找到当前灯数<=m的最前面的房间,这个东西在线段树上二分即可,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
int inf=0x3f3f3f3f;
const int nn = 65536*2;
int tree[nn*2+10];
int _update(int x,int y)
{
	tree[x]=y;x=x/2;
	while(x)
	{
		tree[x]=min(tree[x*2],tree[x*2+1]);
		x=x/2;
	} 
}
int fix;
int _query(int l,int r,int id)
{
	if(tree[id]>fix) return 0;if(l==r) return l;
	if(tree[id*2]<=fix) _query(l,(l+r)/2,id*2);
	else _query((l+r)/2+1,r,id*2+1);
}
int n,m;
pair<int,int> q[100008];
int main()
{
	scanf("%d%d",&n,&m);memset(tree,inf,sizeof(tree));
	for(int i=1;i<=n;i++)
	{
		int x;scanf("%d",&x);
		_update(i+nn-1,x);
	}
	int flag=1;int ui=0;int alr=0;
	for(int i=1;i<=100000;i++)
	{
		if(alr<n) ui+=m;
		while(1)
		{
			fix=ui;
			int t=_query(1,nn,1);
			if(t)
			{
				ui-=tree[nn+t-1];
				_update(nn+t-1,inf);alr++;
			}
			else break;
		}
		q[i]=make_pair(ui,alr);
	}
	int Q;scanf("%d",&Q);
	while(Q--)
	{
		int t;scanf("%d",&t);
		printf("%d %d\n",q[t].second,q[t].first);
	}
	return 0;
}

H:题目链接
首先可以想到两种做法,一种是平衡树维护每个数字区间中元素出现次数(类似权值线段树),合并=启发式合并,查询直接跟线段树差不多,+1操作就直接加入一个长度为1的节点。但是瓶颈在于启发式合并的 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
一种是Trie直接维护二进制表示,查询直接往下遍历,合并=线段树合并,关键就在于如何处理+1操作。首先可以打标记,如果当前标记为奇数,那么交换左右子树并且交换后的左子树标记++,接下来无论奇数和偶数都加上当前标记>>1的值。
然后这道题就在 O ( n l o g n ) O(nlogn) O(nlogn)的时间内解决了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 600005, maxt = 20000005, DEP = 30;
struct Node{
	Node *ls, *rs; int cnt, tag;
	Node() : ls(0), rs(0), cnt(0), tag(0) {}
} nd[maxt];
int par[maxn], n, m, tot;
Node *root[maxn];
void pushdown(Node *x){
	if(!x->tag) return;
	if(x->tag & 1){
		swap(x->ls, x->rs);
		if(x->ls) ++x->ls->tag;
	}
	if(x->ls) x->ls->tag += x->tag >> 1;
	if(x->rs) x->rs->tag += x->tag >> 1;
	x->tag = 0;
}
Node* merge(Node *x, Node *y){
	if(!y) return x;
	if(!x) return y;
	pushdown(x), pushdown(y);
	x->cnt += y->cnt;
	x->ls = merge(x->ls, y->ls);
	x->rs = merge(x->rs, y->rs);
	return x;
}
void update(int x, int d, Node *k){
	if(!d) return;
	if(x & 1){
		if(!k->rs){
			Node *p = nd + (++tot);
			p->cnt = 1;
			k->rs = p;
		}
		update(x >> 1, d - 1, k->rs);
	} else {
		if(!k->ls){
			Node *p = nd + (++tot);
			p->cnt = 1;
			k->ls = p;
		}
		update(x >> 1, d - 1, k->ls);
	}
}
int query(int x, int d, Node *k){
	if(!k) return 0;
	if(!d) return k->cnt;
	pushdown(k);
	if(x & 1) return query(x >> 1, d - 1, k->rs);
	else return query(x >> 1, d - 1, k->ls);
}
int find(int x){return x == par[x] ? x : par[x] = find(par[x]);}
void merge(int x, int y){
	x = find(x), y = find(y);
	if(x != y) par[y] = x;
}
const int maxr = 10000000;
char str[maxr], prt[maxr];
int rpos, ppos, mmx;
char readc(){
	if(!rpos) mmx = fread(str, 1, maxr, stdin);
	if(rpos == mmx) return 0;
	char c = str[rpos++];
	if(rpos == maxr) rpos = 0;
	return c;
}
int read(){
	int x; char c;
	while((c = readc()) < '0' || c > '9');
	x = c - '0';
	while((c = readc()) >= '0' && c <= '9') x = x * 10 + c - '0';
	return x;
}
int print(int x){
	if(x){
		static char sta[10];
		int tp = 0;
		for(; x; x /= 10) sta[tp++] = x % 10 + '0';
		while(tp > 0) prt[ppos++] = sta[--tp];
	} else prt[ppos++] = '0';
	prt[ppos++] = '\n';
}
int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; i++){
		root[i] = nd + (++tot);
		root[i]->cnt = 1;
		update(read(), DEP, root[i]);
		par[i] = i;
	}
	while(m--){
		int opt = read(), a = read(), b, c;
		if(opt == 1){
			b = read();
			if(find(a) == find(b)) continue;
			merge(root[find(a)], root[find(b)]);
			merge(a, b);
		} else if(opt == 3){
			b = read(), c = read();
			print(query(c, b, root[find(a)]));
		} else ++root[find(a)]->tag;
	}
	fwrite(prt, 1, ppos, stdout);
	return 0;
}

I:题目链接
这道题求所有本质不同回文串相加的和mod 1000000007,直接用回文树(回文自动机?)维护一下就行了,复杂度 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 2000005, mod = 1000000007;
int son[maxn][9], len[maxn], num[maxn];
int pw[maxn], fail[maxn], n, tot, last;
char str[maxn];
void init(){
	len[1] = -1;
	fail[0] = 1;
	tot = 1;
}
void extend(int x){
	int p = last, c = str[x] - '1';
	while(str[x - len[p] - 1] != str[x]) p = fail[p];
	if(!son[p][c]){
		int np = ++tot, q = fail[p];
		len[np] = len[p] + 2;
		if(len[np] == 1) num[np] = c + 1;
		else num[np] = (num[p] * 10LL + (ll)pw[len[np] - 1] * (c + 1) + c + 1) % mod;
		while(str[x - len[q] - 1] != str[x]) q = fail[q];
		fail[np] = son[q][c];
		son[p][c] = np;
	}
	last = son[p][c];
}
int main(){
	scanf("%s", str + 1);
	n = strlen(str + 1);
	str[0] = '@';
	str[n + 1] = '#';
	pw[0] = 1;
	for(int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 10LL % mod;
	init();
	for(int i = 1; i <= n; i++) extend(i);
	int res = 0;
	for(int i = 2; i <= tot; i++) res = (res + num[i]) % mod;
	printf("%d\n", res);
	return 0;
}

J:题目链接
考虑对于每一个数都计算f(x),如果x的质因数分解中存在次数大于2的质因数,显然f(x)=0,如果有次数等于2的质因数直接忽略即可,于是就可以用线性筛筛出来,复杂度 O ( n ) O(n) O(n)
我太弱了,给我们队贡献了几个罚时,幸好qzr巨佬及时debug orz!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 20000005, N = 20000000;
ll f[maxn];
int prime[2000005], vis[maxn], T;
int main(){
	int cnt = 0;
	f[1] = 1;
	for(int i = 2; i <= N; i++){
		if(!vis[i]){
			prime[cnt++] = i;
			f[i] = 2;
		}
		for(int j = 0; j < cnt; j++){
			int p = prime[j], m = i * p;
			if(m > N) break;
			vis[m] = 1;
			if(i % p == 0){
				int d = i / p;
				if(d % p == 0) f[m] = 0;
				else f[m] = f[d];
				break;
			}
			f[m] = f[i] * f[p];
		}
	}
	for(int i = 1; i <= N; i++){
		f[i] += f[i - 1];
	}
	scanf("%d", &T);
	while(T--){
		int n; scanf("%d", &n);
		printf("%lld\n", f[n]);
	}
	return 0;
}

K:题目链接
这题真的可惜啊……cy巨佬已经想到做法了,可惜比赛中间发呆(分薯片?)了一会儿(雾)……
考虑每种个数的石子出现了多少次,由于f显然会出现一个循环,因此用高精度做一下就好了2333……接下来就到了dp时间,令f[i][j]表示所有个数<=i的堆中,异或和恰好为j的方案有多少种。我们会发现第i种个数有没有贡献仅仅取决于其取奇数堆还是偶数堆,也就是求:
( 0 n ) + ( 2 n ) + ( 4 n ) + ⋯ \binom{0}{n}+\binom{2}{n}+\binom{4}{n}+\cdots (n0)+(n2)+(n4)+
可以证明这个玩意儿等于 2 n − 1 2^{n-1} 2n1,于是用费马小定理+快速幂就可以算出来了,复杂度 O ( k 2 ) O(k^2) O(k2)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 1500005, maxm = 4100, mod = 1000000007;
const int Base = 1000000000, Digit = 9;
struct Bigint{
	int num[maxn], n;
	Bigint(){memset(num, 0, sizeof(num)); n = 0;}
	Bigint(char *str){
		int l = strlen(str);
		memset(num, 0, sizeof(num));
		n = 0;
		for(int i = l - 1; i >= 0; i -= Digit){
			int k = min(Digit, i + 1);
			for(int j = i - k + 1; j <= i; j++)
				num[n] = num[n] * 10 + str[j] - '0';
			++n;
		}
	}
	Bigint& operator-=(int x){
		num[0] -= x;
		for(int i = 0; i < n; i++){
			if(num[i] < 0){
				num[i] += Base;
				--num[i + 1];
			}
		}
		while(!num[n - 1]) --n;
		return *this;
	}
	int operator/=(int x){
		int rem = 0;
		for(int i = n - 1; i >= 0; i--){
			ll t = (ll)rem * Base + num[i];
			num[i] = t / x;
			rem = t % x;
		}
		return rem;
	}
} big;
char str[maxn * 4];
int K, A, B, C, D, E, X1, cnt[maxm], f[2][maxm], vis[maxm];
void solve_small(){
	int n = big.num[0];
	memset(cnt, -1, sizeof(cnt));
	for(int i = 0; i < n - 1; i++){
		ll x = X1;
		++cnt[X1];
		X1 = ((((A * x + B) * x + C) * x + D) * x + E - 1) % K + 1;
	}
	++cnt[X1];
}
void solve_big(){
	int n = 0;
	while(true){
		ll x = X1;
		vis[x] = ++n;
		X1 = ((((A * x + B) * x + C) * x + D) * x + E - 1) % K + 1;
		if(vis[X1]) break;
	}
	if(vis[X1] > 1) big -= vis[X1] - 1;
	int rem = big /= n - vis[X1] + 1;
	int md = big /= mod - 1, md1 = (md - 1 + mod) % mod;
	memset(cnt, -1, sizeof(cnt));
	for(int i = 1; i <= K; i++) if(vis[i]){
		if(vis[i] >= vis[X1]){
			if(vis[i] < vis[X1] + rem) cnt[i] = md;
			else cnt[i] = md1;
		} else cnt[i] = 0;
	}
}
ll modpow(ll a, int b){
	ll res = 1;
	for(; b; b >>= 1){
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
	}
	return res;
}
int main(){
	scanf("%s%d%d%d%d%d%d%d", str, &X1, &A, &B, &C, &D, &E, &K);
	big = str;
	if(big.n == 1 && big.num[0] <= K) solve_small();
	else solve_big();
	f[0][0] = 1;
	for(int i = 1; i <= K; i++){
		int *now = f[i & 1], *lst = f[~i & 1];
		if(cnt[i] < 0){
			for(int j = 0; j <= K; j++) now[j] = lst[j];
			continue;
		}
		ll t = modpow(2, cnt[i]);
		for(int j = 0; j <= K; j++)
			now[j] = t * (lst[j] + lst[j ^ i]) % mod;
	}
	int res = 0;
	for(int i = 1; i <= K; i++) res = (res + f[K & 1][i]) % mod;
	printf("%d\n", res);
	return 0;
}

L:题目链接
这道题是图论,还是比较水的吧……(完全靠stl的priority_queue跑得快2333)
考虑一个dp状态f[i][j]表示从1到i恰好修改j条路的最短路是多少,然后在Dijkstra(不敢写SPFA,怕被卡增加罚时)算最短路的时候更新一下dp就行了,复杂度 O ( T m K l o g n ) O(TmKlogn) O(TmKlogn)(我也不造咋跑过去的2333,反正就是800+msA掉了)

#include<bits/stdc++.h>
#define pii pair< int,int> 
#define ll long long
#define pli pair<long long,int>
using namespace std;
int buf[80];
template<class T> inline bool getd(T& x){
    int ch=getchar();
    bool neg=false;
    while(ch!=EOF && ch!='-' && !isdigit(ch)) ch=getchar();
    if(ch==EOF) return false;
    if(ch=='-'){
        neg=true;
        ch=getchar();
    }
    x=ch-'0';
    while(isdigit(ch=getchar())) x=x*10+ch-'0';
    if(neg) x=-x;
    return true;
}

template<class M> inline void putd(M x)
{
    int p=0;
    if(x<0){
        putchar('-');
        x=-x;
    }
    do{
        buf[p++]=x%(ll)10;
        x/=(ll)10;
    }while(x);
    for(int i=p-1;i>=0;i--) putchar(buf[i]+'0');
    putchar('\n');
}
int n,m,k;
ll inf=0x3f3f3f3f3f3f3f3f;
ll dp[100008][11];
vector<int> v[100008],co[100008];
int main()
{
	int T;getd(T);
	while(T--)
	{
		memset(dp,inf,sizeof(dp));
		getd(n);getd(m);getd(k);
		for(int i=1;i<=n;i++)
		{
			v[i].clear();co[i].clear();
		}
		while(m--)
		{
			int x,y,s;getd(x);getd(y);getd(s);
			v[x].push_back(y);co[x].push_back(s);
		}priority_queue<pli> nxt;
		for(int ck=0;ck<=k;ck++)
		{
			dp[1][ck]=0;
			priority_queue<pli> pq;swap(pq,nxt);
			pq.push(make_pair(-0,1));
			while(!pq.empty())
			{
				int t=pq.top().second;ll dist=pq.top().first;
				pq.pop();
				if(dist>dp[t][ck]) continue;
				for(int i=0;i<v[t].size();i++)
				{
					int u=v[t][i];int add=co[t][i];
					if(dp[u][ck]>dp[t][ck]+add)
					{
						dp[u][ck]=dp[t][ck]+add;
						pq.push(make_pair(-dp[u][ck],u));
					}
					if(dp[u][ck+1]>dp[t][ck])
					{
						dp[u][ck+1]=dp[t][ck];
						nxt.push(make_pair(-dp[u][ck+1],u));
					}
				}
			}
		}
		putd(dp[n][k]);
	}
}

然后整场预赛就结束了……总体来说发挥已经很不错了2333!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值