- 8.6 2020 Multi-University Training Contest 6题解及补题
- 比赛过程
- 题解
- 1001 Road To The 3rd Building
- 题意
- 解法
- 代码
- 1002 Little Rabbit's Equation
- 题意
- 解法
- 代码
- 1005 Fragrant numbers
- 题意
- 解法
- 代码
- 1006 A Very Easy Graph Problem
- 题意
- 解法
- 代码
- 1009 Divisibility
- 题意
- 解法
- 代码
- 1010 Expectation
- 题意
- 解法
- 代码
- 1001 Road To The 3rd Building
8.6 2020 Multi-University Training Contest 6题解及补题
比赛过程
这场算是比较顺利的场,过了四道题,还是需要更加深入的配合。
题解
1001 Road To The 3rd Building
题意
给你n个数,现在任意选择一个区间求其平均值,你需要求出这个平均值的期望
解法
找规律,发现就是逆元的运算。
用到上次题解的逆元求1/i的逆元和
代码
#include
using namespace std;
typedef long long ll;
ll gcd(ll T, ll b) { return b == 0 ? T : gcd(b, T % b); }
ll lcm(ll T, ll b) { return T / gcd(T, b) * b; }
#define IO ios::sync_with_stdio(false), cin.tie(0),cout.tie(0)
// int a[200020];
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n)-1; i >= a; --i)
// ll mod= 1000000007;
const int maxn=200020;
ll sum[maxn];
ll mod = 1e9 + 7;
ll inv[maxn];
void getInv(int n){
inv[1] = 1;
for (int i = 2; i <= n; i++) inv[i] = inv[mod % i] * (mod - mod / i) % mod;
}
ll cnt[maxn];
void init() {
getInv(maxn);
sum[1] = 1;
REP(i, 2, maxn) { sum[i] = (sum[i - 1] + inv[i]) % mod; }
return ;
}
void test(ll n) {
cnt[1]=sum[n];
for(ll i=2;i<=(n+1)/2;i++) {
cnt[i]=cnt[i-1];
cnt[i]=(cnt[i]+sum[n+1-i]-sum[i-1]+mod)%mod;
}
}
int main() {
IO;
int T;
cin>>T;
init();
while(T--) {
int a[200020];
int n;
ll ans=0;
cin>>n;
test(n);
for(int i=1;i<=n;i++) {
cin>>a[i];
}
// cout<
1002 Little Rabbit's Equation
题意
判断给出的表达式在哪种进制下成立。
解法
直接把字符串截成三段,从小到大枚举每种进制即可。
代码
#include
using namespace std;
typedef long long ll;
ll op(string s,int r){
int len=s.length();
ll ans=0;
ll tmp=1;
for(int i=len-1;i>=0;i--){
int x;
if(s[i]>='0'&&s[i]<='9'){
x=s[i]-'0';
}
else{
x=s[i]-'A'+10;
}
if(x>=r){
return -1;
}
ans+=tmp*x;
tmp*=r;
}
return ans;
}
ll ff(ll a,ll b,int p){
if(p==1)return a+b;
if(p==2)return a-b;
if(p==3)return a*b;
if(p==4){
if(a%b==0)return a/b;
else return -1;
}
}
int main() {
string s;
while(cin>>s){
string a,b,c;
ll aa,bb,cc;
int p=0;
int p1,p2;
int len=s.length();
for(int i=0;i
1005 Fragrant numbers
题意
给一个无限循环的字符串1145141919,可以取一个前缀并在中间添加+,*(,)使其变成一个运算表达式,
每次询问一个n,输出构成表达式结果等于n的最小前缀。如果不存在输出-1
解法
经过打表处理可以发现,只需要前13个以内就可以构造出所有合法状态,然后就是区间dp了。要
枚举区间长度,左端点和右端点,枚举$a[l][mid],a[mid+1][r]的状态。
代码
#include
using namespace std;
typedef long long ll;
const int mac=50;
int a[mac],ans[5050];
vectordp[50][50];
bool vis[50][50][5010];
ll get_num(int l,int r)
{
ll ans=0;
for (int i=l; i<=r; i++)
ans=ans*10+a[i];
return ans;
}
int main(int argc, char const *argv[])
{
string s="1145141919";
s+=s;
int len=s.length();
for (int i=0; i5000) continue;
dp[i][j].push_back(p);
vis[i][j][p]=1;
}
}
for (int i=2; i<=15; i++){
for (int l=1; l+i-1<=15; l++){
int r=l+i-1;
for (int mid=l; mid<=r; mid++){
for (auto x:dp[l][mid]){
for (auto y:dp[mid+1][r]){
if (x+y<=5000 && !vis[l][r][x+y]){
dp[l][r].push_back(x+y);
vis[l][r][x+y]=1;
}
if (x*y<=5000 && !vis[l][r][x*y]){
dp[l][r].push_back(x*y);
vis[l][r][x*y]=1;
}
}
}
}
}
}
for (int i=1; i<=15; i++){
for (auto x:dp[1][i]){
if (x<=5000 && !ans[x]) ans[x]=i;
}
}
int t;
scanf ("%d",&t);
while (t--){
int n;
scanf ("%d",&n);
if (ans[n]) printf("%d\n",ans[n]);
else printf("-1\n");
}
return 0;
}
1006 A Very Easy Graph Problem
题意
在一个n个结点,m条边的无向连通图中,且第i条边的权值为2i,每个结点有一个值,为1或者0。d(i,j)表示结点i到结点j之间的最短距离。对所有节点求所有的可能配对形式d(i,j)*[a[i]1^a[0]0]的和,最后对1e9+7求余。
解法
注意这个权值是比较特殊的,也就是如果第i条边(权值为2i)是连接结点j和结点k,但是在这之前j和k已经相连了,那么这条边(权值2i)永远都不会被最短路经过,因为前i-1条边的总权值是2i-2,小于2i.所以我们便可以将这个无向连通图转换为一棵最小生成树(既是最小生成树也是任意两个结点之间的距离都是最小)。这里的n,m都是1e5的数量级别,所以如果暴力求两个结点的最短路那么肯定会超时。所以我们可以转换一下思路,求每条边的贡献值,也就是如果这条边最后会出现在最短路中,我们需要计算的是在最短路中出现了多少次,可以求出这条边两边各自的1和0的数量,那么这条边在最短路中的次数就是left_0right_1+left_1right_0,那么这条边的在最后的结果中的贡献就是(left_0right_1+left_1right_0)*这条边的权值。那么现在主要的就是求出每条边两边的1和0的数量,可以使用dfs进行求解。
代码
#include
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 5;
const int inf = ~0u >> 1;
typedef pair P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n) - 1; i >= a; --i)
const ll mod = 1e9 + 7;
vector > e[maxn];
int a[maxn];
int fa[maxn];
ll ans = 0, zero, one;
int init() {
ans = zero = one = 0;
REP(i, 0, maxn) {
fa[i] = i;
e[i].clear();
}
}
int find(int x) //用于查找一个元素的所在集合即根节点
{
if (fa[x] != x) fa[x] = find(fa[x]); //路径压缩,递归将路径上的元素父亲指针都指向根节点
return fa[x];
}
void merge(int r1, int r2) //合并两个集合,r2合并到r1
{
r1 = find(r1);
r2 = find(r2);
if (r1 != r2) fa[r2] = r1;
}
pair dfs(int rt,int fa,long long edge){
pair tmp(0,0);
for(int i = 0; i < e[rt].size(); ++i){
int to = e[rt][i].first;
if(to == fa) continue;
pair tmp2 = dfs(to,rt,e[rt][i].second);
tmp.first += tmp2.first;
tmp.second += tmp2.second;
}
if(a[rt] == 0) tmp.first ++;
else tmp.second ++;
ans += ((1ll * (zero - tmp.first) * tmp.second + 1ll * (one - tmp.second) * tmp.first) % mod * edge % mod);
return tmp;
}
int main() {
IO;
int t;
cin >> t;
while (t--) {
init();
int n, m;
cin >> n >> m;
REP(i, 1, n + 1) {
cin >> a[i];
if (a[i]) {
one++;
}
else {
zero++;
}
}
ll cal = 1;
REP(i, 1, m + 1) {
cal = cal * 2 % mod;
int u, v;
cin >> u >> v;
if (find(u) != find(v)) {
e[u].push_back(pair(v, cal));
e[v].push_back(pair(u, cal));
merge(u, v);
}
}
dfs(1, -1, 0);
cout << ans % mod << endl;
}
return 0;
}
1009 Divisibility
题意
给你一个10进制的b和x,对于任意的一个b进制的y。如果y每一位的和可以被x整除,且y可以被x整除;或者如果y每一位的和不可以被x整除,且y不可以被x整除。那么就输出T。否则输出F
解法
思维题,发现b--之后,b取余x等于0就T的。
代码
#include
using namespace std;
typedef long long ll;
int main() {
int t;
scanf("%d",&t);
while(t--){
ll b,x;
scanf("%lld%lld",&b,&x);
b--;
if(b%x==0){
cout<<"T"<
1010 Expectation
题意
给一个图,输出其随机生成树的权值的期望,其权值为树的所有边的按位与。
解法
用到了矩阵树定理和期望的知识。矩阵树定理可以求出一个图内生成树的数目,也就是求期望要用到的总数。
分子则是所有边的按位与。因为E(x+y)=E(x)+E(y),所以可以按照二进制的位数分别计算31位每一位的贡献,
遍历每一位,每次都用矩阵树定理算一次答案即可。
代码
#include
using namespace std;
#define ll long long
const ll mod=998244353;
ll n,m,t,sum;
ll f[207][207];
ll u[40007],v[40007],w[40007];
ll gauss () {
ll ans = 1 ;
for (int i = 1; i < n; i ++) {
for (int j = i + 1; j < n; j ++) {
while (f[j][i]) {
ll t = f[i][i] / f[j][i] ;
for (int k = i; k < n; k ++)
f[i][k] = (f[i][k] - t * f[j][k] + mod) % mod ;
swap (f[i], f[j]) ;
ans = -ans ;
}
}
ans = (ans * f[i][i]) % mod ;
}
return (ans + mod) % mod ;
}
long long int pow(long long int x,long long int n,long long int mod)
{
long long int res=1;
while(n>0)
{
if(n%2==1)
{
res=res*x;
res=res%mod;
}
x=x*x;
x=x%mod;
n>>=1;
}
return res;
}
void add(int u,int v){
f[u][u]++;f[v][v]++;
f[u][v]--;f[v][u]--;
}
void init(){
memset(f,0,sizeof(f));
}
int main(){
scanf("%lld",&t);
while(t--){
init();
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i]>>w[i];
add(u[i],v[i]);
}
sum=gauss();
sum=pow(sum,mod-2,mod);
ll aqours=0,er=1;
for(int i=0;i<=30;i++){
init();
for(int i=1;i<=m;i++){
if(w[i]&er){
add(u[i],v[i]);
}
}
aqours=(aqours+gauss()*er%mod*sum%mod)%mod;
er*=2;
}
printf("%lld\n",aqours);
}
}